Skip to main content

@zod Comment Annotations

Use triple-slash Prisma doc comments with @zod to append validations.

model User {
id String @id @default(cuid())
/// @zod .email().min(5)
email String @unique
}

Result:

export const UserSchema = z
.object({
email: z.string().email().min(5),
// ...
})
.strict();

Annotations are concatenated after base type; unsafe expressions are not executed (string append model). Keep rules pure.

External Validators with @zod.import

Bring in runtime helpers directly from doc comments when you need logic that lives outside the generated file.

model User {
id String @id @default(cuid())
/// @zod.import(["import { isEmail } from '../validators/email'\"])
/// @zod.custom.use(z.string().refine((val) => isEmail(val), { message: 'Invalid email' }))
email String @unique
}

Generated schema (Pure Variant excerpt):

import { isEmail } from '../validators/email';

export const UserSchema = z
.object({
email: z.string().refine((val) => isEmail(val), {
message: 'Invalid email',
}),
// ...
})
.strict();
  • Provide one or more complete import statements inside the array. Relative paths are kept intact and rewritten per output directory.

  • Imports must produce runtime values. Type-only specifiers (e.g. import type { Validator } ... or type Foo as Bar) are detected and omitted, ensuring we never emit imports that disappear after compilation.

  • Field-level imports are merged with model-level imports. When the same statement appears multiple times it is emitted once.

  • Model-level imports can also supply chained refinements:

    /// @zod.import(["import { assertCompanyDomain } from '../validators/domain'\"]).refine(assertCompanyDomain)
    model Organisation {
    id String @id @default(cuid())
    email String @unique
    }

When both @zod.import() and other @zod.* annotations are present, the generator keeps custom schemas from @zod.custom.use() as the base and only appends inline validations when they do not replace the base type.

String Format Validations

The generator supports all Zod v4 string format validation methods. In Zod v4, these generate optimized base types (e.g., z.email() instead of z.string().email()).

Network & URL Formats

model WebData {
id String @id @default(cuid())
/// @zod.email()
email String @unique
/// @zod.url()
website String?
/// @zod.httpUrl()
apiUrl String
/// @zod.hostname()
server String
}

Generated schema (Zod v4):

export const WebDataCreateInputSchema = z.object({
email: z.email(), // Base type in v4
website: z.url().optional(),
apiUrl: z.httpUrl(), // Base type in v4
server: z.hostname(), // Base type in v4
});

Identifier Formats

model Identifiers {
id String @id @default(cuid())
/// @zod.uuid()
uuid String
/// @zod.nanoid()
nanoid String
/// @zod.cuid()
cuid String
/// @zod.cuid2()
cuid2 String
/// @zod.ulid()
ulid String
}

Generated schema (Zod v4):

export const IdentifiersCreateInputSchema = z.object({
uuid: z.uuid(), // Base type in v4
nanoid: z.nanoid(), // Base type in v4
cuid: z.cuid(), // Base type in v4
cuid2: z.cuid2(), // Base type in v4
ulid: z.ulid(), // Base type in v4
});

Encoding & Character Formats

model EncodingData {
id String @id @default(cuid())
/// @zod.base64()
base64 String
/// @zod.base64url()
base64url String
/// @zod.hex()
hex String
/// @zod.emoji()
reaction String
}

Generated schema (Zod v4):

export const EncodingDataCreateInputSchema = z.object({
base64: z.base64(), // Base type in v4
base64url: z.base64url(), // Base type in v4
hex: z.hex(), // Base type in v4
reaction: z.emoji(), // Base type in v4
});

Security & Network Formats

model SecurityData {
id String @id @default(cuid())
/// @zod.jwt()
token String?
/// @zod.hash("sha256")
checksum String
/// @zod.ipv4()
ipv4Addr String
/// @zod.ipv6()
ipv6Addr String?
/// @zod.cidrv4()
subnet String
/// @zod.datetime()
timestamp String
}

Generated schema (Zod v4):

export const SecurityDataCreateInputSchema = z.object({
token: z.jwt().optional(), // Base type in v4
checksum: z.hash("sha256"), // Base type with parameter
ipv4Addr: z.ipv4(), // Base type in v4
ipv6Addr: z.ipv6().optional(), // Base type in v4
subnet: z.cidrv4(), // Base type in v4
timestamp: z.datetime(), // Base type in v4
});

Chaining with String Format Methods

String format methods can be chained with other Zod validations:

model ChainedValidations {
id String @id @default(cuid())
/// @zod.email().max(100).toLowerCase()
email String @unique
/// @zod.nanoid().min(21)
publicId String
/// @zod.httpUrl().max(200)
website String?
}

Generated schema (Zod v4):

export const ChainedValidationsCreateInputSchema = z.object({
email: z.email().max(100).toLowerCase(),
publicId: z.nanoid().min(21),
website: z.httpUrl().max(200).optional(),
});

Version Compatibility

The generator automatically detects your Zod version:

  • Zod v4: Uses optimized base types like z.email(), z.nanoid()
  • Zod v3: Falls back to chaining methods like z.string().email() where supported, or z.string() for unsupported methods

Regular Expression Validation

model ValidationData {
id String @id @default(cuid())
/// @zod.regex(/^[A-Z]+$/, "Must be uppercase letters only")
upperCode String
/// @zod.regex(/^\d{4}-\d{2}-\d{2}$/)
dateFormat String
/// @zod.regex(new RegExp('[0-9]+'))
numberStr String
}

Generated schema (Zod v4):

export const ValidationDataCreateInputSchema = z.object({
upperCode: z.regex(/^[A-Z]+$/, 'Must be uppercase letters only'),
dateFormat: z.regex(/^\d{4}-\d{2}-\d{2}$/),
numberStr: z.regex(new RegExp('[0-9]+')),
});

Advanced Parameter Support

The generator supports complex parameter expressions, including:

JavaScript Object Literals

model AdvancedValidation {
id String @id @default(cuid())
/// @zod.nanoid({ abort: true, error: 'Custom nanoid error' })
customId String
/// @zod.nanoid({ abort: true, pattern: new RegExp('.'), error: 'Complex validation' })
complexId String
}

Generated schema:

export const AdvancedValidationCreateInputSchema = z.object({
customId: z.nanoid({"abort":true,"error":"Custom nanoid error"}),
complexId: z.nanoid({"abort":true,"pattern":new RegExp('.'),"error":"Complex validation"}),
});

Parameter Types Supported

The generator preserves all JavaScript parameter types:

  • Strings: @zod.nanoid('Custom error')z.nanoid('Custom error')
  • Objects: @zod.nanoid({ abort: true })z.nanoid({"abort":true})
  • Numbers: @zod.min(10)z.string().min(10) (valid usage)
  • Booleans: @zod.optional()z.string().optional() (parameter-less)
  • Arrays: @zod.custom([1, 2, 3])z.custom([1,2,3])
  • RegExp: @zod.regex(/pattern/)z.regex(/pattern/)
  • Function calls: @zod.custom(Date.now())z.custom(Date.now())
  • Nested expressions: @zod.custom(new RegExp('.'))z.custom(new RegExp('.'))

Complete Reference

MethodParametersDescriptionZod v4 OutputZod v3 Fallback
@zod.email()Optional error message/configEmail validationz.email()z.string().email()
@zod.url()Optional error message/configURL validationz.url()z.string().url()
@zod.httpUrl()Optional error message/configHTTP/HTTPS URLsz.httpUrl()z.string()
@zod.hostname()Optional error message/configHostname validationz.hostname()z.string()
@zod.uuid()Optional error message/configUUID validationz.uuid()z.string().uuid()
@zod.datetime()Optional error message/configISO datetime validationz.datetime()z.string().datetime()
@zod.nanoid()Optional config object/error messageNanoid validationz.nanoid()z.string()
@zod.cuid()Optional error message/configCUID validationz.cuid()z.string()
@zod.cuid2()Optional error message/configCUID v2 validationz.cuid2()z.string()
@zod.ulid()Optional error message/configULID validationz.ulid()z.string()
@zod.base64()Optional config object/error messageBase64 validationz.base64()z.string()
@zod.base64url()Optional config object/error messageBase64URL validationz.base64url()z.string()
@zod.hex()Optional error message/configHexadecimal validationz.hex()z.string()
@zod.jwt()Optional config object/error messageJWT validationz.jwt()z.string()
@zod.regex()Pattern, optional error messageRegular expression validationz.regex(/pattern/)z.string().regex(/pattern/)
@zod.hash("algo")Algorithm, optional configHash validationz.hash("sha256")z.string()
@zod.ipv4()Optional error message/configIPv4 validationz.ipv4()z.string()
@zod.ipv6()Optional error message/configIPv6 validationz.ipv6()z.string()
@zod.cidrv4()Optional error message/configCIDR v4 validationz.cidrv4()z.string()
@zod.emoji()Optional error message/configSingle emoji validationz.emoji()z.string()

Custom Inline Override (@zod.custom.use)

Replace an entire field schema inline:

model AiChat {
id String @id @default(cuid())
/// @zod.custom.use(z.array(z.object({ role: z.enum(['user','assistant','system']), parts: z.array(z.object({ type: z.enum(['text','image']), text: z.string() })) })))
messages Json @default("[]")
}

Result (excerpt):

messages: z.array(
z.object({
role: z.enum(['user', 'assistant', 'system']),
parts: z.array(z.object({ type: z.enum(['text', 'image']), text: z.string() })),
}),
).default('[]');

This short-circuits other annotations for that field.

Optional helper for deep JSON arrays:

import { jsonMaxDepthRefinement } from 'prisma-zod-generator';
const ChatMessagesSchema = z.array(MessageSchema)${'${jsonMaxDepthRefinement(10)}'};

Custom Object Schema (@zod.custom)

For JSON fields, use @zod.custom() to define structured object schemas using JavaScript object literals:

model User {
id String @id @default(cuid())

/// @zod.custom({ "title": "User Profile", "description": "User details", "isActive": true })
profile Json

/// @zod.custom({ "settings": { "theme": "dark", "notifications": true }, "preferences": ["email", "sms"] })
metadata Json
}

Result:

// Creates type-safe object schemas
profile: z.union([JsonNullValueInputSchema, z.object({
title: z.string(),
description: z.string(),
isActive: z.boolean()
})]).optional(),

metadata: z.union([JsonNullValueInputSchema, z.object({
settings: z.object({
theme: z.string(),
notifications: z.boolean()
}),
preferences: z.array(z.string())
})]).optional()

Supported Value Types

  • Stringsz.string()
  • Numbersz.number().int() or z.number()
  • Booleansz.boolean()
  • Arraysz.array(T) (inferred from first element)
  • Nested Objectsz.object({...})
  • nullz.null()

Key Differences from @zod.custom.use()

Feature@zod.custom()@zod.custom.use()
Use CaseStructured object/array schemasComplete schema replacement with custom logic
SyntaxJavaScript object literalsRaw Zod schema expressions
Field TypesJSON fields onlyAny field type
ComplexitySimple structured dataAdvanced validation (refine, transform)
Auto-conversion✅ Automatic type inference❌ Manual Zod syntax

Example Comparison

// @zod.custom() - Simple structured schema
/// @zod.custom({ "name": "John", "age": 30, "active": true })
profile Json

// @zod.custom.use() - Advanced custom validation
/// @zod.custom.use(z.object({ name: z.string().min(2), age: z.number().positive() }).refine(data => data.age >= 18))
profile Json

Native Type Max Length Validation

The generator automatically extracts max length constraints from database native types and applies them as Zod .max() validations.

Supported Native Types

DatabaseNative TypesExampleGenerated Validation
PostgreSQLVarChar(n), Char(n)@db.VarChar(255)z.string().max(255)
MySQLVarChar(n), Char(n)@db.VarChar(100)z.string().max(100)
SQL ServerVarChar(n), Char(n), NVarChar(n), NChar(n)@db.NVarChar(500)z.string().max(500)
SQLiteNo length constraints-No auto-validation
MongoDBObjectId@db.ObjectIdz.string().max(24)

Basic Usage

model User {
id String @id @default(cuid())
email String @db.VarChar(320) // → z.string().max(320)
displayName String? @db.VarChar(100) // → z.string().max(100).optional()
bio String? @db.Char(500) // → z.string().max(500).optional()
}

Generated schema:

export const UserCreateInputSchema = z.object({
email: z.string().max(320),
displayName: z.string().max(100).optional(),
bio: z.string().max(500).optional(),
});

Conflict Resolution with @zod Comments

When both native types and @zod.max() exist, the more restrictive constraint is used:

model Product {
id String @id @default(cuid())

// Native type wins (more restrictive)
shortName String @db.VarChar(50) /// @zod.max(100)

// @zod wins (more restrictive)
description String? @db.VarChar(1000) /// @zod.max(500)

// Only native constraint
category String @db.VarChar(80)

// Only @zod constraint
tags String? /// @zod.max(200)
}

Generated result:

export const ProductCreateInputSchema = z.object({
shortName: z.string().max(50), // Native type (50) < @zod (100)
description: z.string().max(500).optional(), // @zod (500) < Native (1000)
category: z.string().max(80), // Only native constraint
tags: z.string().max(200).optional(), // Only @zod constraint
});

Complex Validations

Native constraints work alongside other @zod validations:

model Account {
id String @id @default(cuid())
/// @zod.email().toLowerCase()
email String @unique @db.VarChar(320)
/// @zod.min(8).regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
password String @db.VarChar(255)
}

Generated schema:

export const AccountCreateInputSchema = z.object({
email: z.string().max(320).email().toLowerCase(),
password: z
.string()
.max(255)
.min(8)
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
});

Array Support

Native constraints are applied to array elements:

model Tags {
id String @id @default(cuid())
names String[] @db.VarChar(50) // Each string max 50 chars
}

Generated schema:

export const TagsCreateInputSchema = z.object({
names: z.string().max(50).array(),
});

Edge Cases

The generator handles various edge cases gracefully:

  • Zero length: @db.VarChar(0) is ignored (invalid constraint)
  • Very large lengths: @db.VarChar(65535) works normally
  • Multiple max constraints: Duplicate .max() calls are consolidated into the most restrictive value
  • Non-string fields: Native types on Int/DateTime/etc. are ignored for max length extraction
  • Text types: @db.Text (without length parameter) doesn't generate max constraints

MongoDB ObjectId

MongoDB ObjectId fields automatically get a max length of 24 characters:

// MongoDB schema
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
}

Generated schema:

export const UserCreateInputSchema = z.object({
id: z.string().max(24), // ObjectId is 24 hex chars
email: z.string(),
});