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.

Complete Feature Reference

String Validations

Length & Content Validation

model StringValidation {
id String @id @default(cuid())
/// @zod.min(2, "Too short")
name String
/// @zod.max(100)
title String
/// @zod.length(10)
code String
/// @zod.includes("@")
email String
/// @zod.startsWith("https://")
website String
/// @zod.endsWith(".com")
domain String
/// @zod.regex(/^[A-Z]+$/, "Must be uppercase")
acronym String
}

String Transformation

model StringTransform {
id String @id @default(cuid())
/// @zod.trim()
cleaned String
/// @zod.toLowerCase()
slug String
/// @zod.toUpperCase()
code String
/// @zod.uppercase()
acronym String
/// @zod.lowercase()
text String
/// @zod.normalize()
normalized String
}

Standard Format Validation

model StandardFormats {
id String @id @default(cuid())
/// @zod.email("Invalid email format")
email String @unique
/// @zod.url()
website String?
/// @zod.uuid()
reference String
/// @zod.datetime()
timestamp String
/// @zod.ip()
ipAddress String
/// @zod.cidr()
network String
/// @zod.date()
dateStr String
/// @zod.time()
timeStr String
/// @zod.duration()
period String
}

Zod v4 String Format Methods

The generator automatically detects your Zod version and uses optimized base types in v4.

Network & URL Formats

model NetworkFormats {
id String @id @default(cuid())
/// @zod.httpUrl()
apiUrl String
/// @zod.hostname()
server String
/// @zod.ipv4()
ipAddress String
/// @zod.ipv6()
ipv6Addr String?
/// @zod.cidrv4()
subnet String
/// @zod.cidrv6()
subnet6 String?
}

Generated schema (Zod v4):

export const NetworkFormatsCreateInputSchema = z.object({
apiUrl: z.httpUrl(), // Base type in v4
server: z.hostname(), // Base type in v4
ipAddress: z.ipv4(), // Base type in v4
ipv6Addr: z.ipv6().optional(),
subnet: z.cidrv4(),
subnet6: z.cidrv6().optional(),
});

Identifier Formats

model Identifiers {
id String @id @default(cuid())
/// @zod.guid()
guid 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({
guid: z.guid(), // 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 & Crypto Formats

model SecurityData {
id String @id @default(cuid())
/// @zod.jwt()
token String?
/// @zod.hash("sha256")
checksum 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
});

ISO Date/Time Formats

model ISOFormats {
id String @id @default(cuid())
/// @zod.isoDate()
date String
/// @zod.isoTime()
time String
/// @zod.isoDatetime()
datetime String
/// @zod.isoDuration()
duration String
}

Generated schema (Zod v4):

export const ISOFormatsCreateInputSchema = z.object({
date: z.iso.date(), // ISO methods use z.iso namespace
time: z.iso.time(),
datetime: z.iso.datetime(),
duration: z.iso.duration(),
});

Number Validations

model NumberValidation {
id String @id @default(cuid())
/// @zod.min(0, "Cannot be negative")
score Int
/// @zod.max(100)
percent Int
/// @zod.gt(0, "Must be greater than 0")
revenue Float
/// @zod.gte(0, "Cannot be negative")
assets Float
/// @zod.lt(100, "Must be less than 100")
discount Float
/// @zod.lte(100, "Cannot exceed 100")
capacity Float
/// @zod.step(0.01, "Must be in 0.01 increments")
price Float
/// @zod.positive("Must be positive")
amount Float
/// @zod.negative()
debt Float?
/// @zod.nonnegative()
balance Float
/// @zod.nonpositive()
loss Float?
/// @zod.int()
whole Float
/// @zod.finite()
measured Float
/// @zod.safe()
counter Int
/// @zod.multipleOf(5, "Must be multiple of 5")
rating Int
}

Generated schema:

export const NumberValidationCreateInputSchema = z.object({
score: z.number().int().min(0, "Cannot be negative"),
percent: z.number().int().max(100),
revenue: z.number().gt(0, "Must be greater than 0"),
assets: z.number().gte(0, "Cannot be negative"),
discount: z.number().lt(100, "Must be less than 100"),
capacity: z.number().lte(100, "Cannot exceed 100"),
price: z.number().multipleOf(0.01, "Must be in 0.01 increments"),
amount: z.number().positive("Must be positive"),
debt: z.number().negative().optional(),
balance: z.number().nonnegative(),
loss: z.number().nonpositive().optional(),
whole: z.number().int(),
measured: z.number().finite(),
counter: z.number().int().safe(),
rating: z.number().int().multipleOf(5, "Must be multiple of 5"),
});

Array Validations

model ArrayValidation {
id String @id @default(cuid())
/// @zod.min(1, "At least one item required")
tags String[]
/// @zod.max(10)
items String[]
/// @zod.length(3)
coords Float[]
/// @zod.nonempty()
colors String[]
/// @zod.nullable()
options String[]?
}

Generated schema:

export const ArrayValidationCreateInputSchema = z.object({
tags: z.string().array().min(1, "At least one item required"),
items: z.string().array().max(10),
coords: z.number().array().length(3),
colors: z.string().array().nonempty(),
options: z.string().array().nullable().optional(),
});

Date Validations

model DateValidation {
id String @id @default(cuid())
/// @zod.min(new Date('2020-01-01'))
startDate DateTime
/// @zod.max(new Date('2030-12-31'))
endDate DateTime
}

Field Modifiers

model FieldModifiers {
id String @id @default(cuid())
/// @zod.optional()
description String
/// @zod.nullable()
notes String?
/// @zod.nullish()
metadata String?
/// @zod.default("active")
status String
}

Advanced Modifiers

model AdvancedModifiers {
id String @id @default(cuid())
/// @zod.catch("fallback")
safeData String
/// @zod.pipe(z.string().transform(s => s.toUpperCase()))
processed String
/// @zod.brand<"UserId">()
userId String
/// @zod.readonly()
immutable String
}

Generated schema:

export const AdvancedModifiersCreateInputSchema = z.object({
safeData: z.string().catch("fallback"),
processed: z.string().pipe(z.string().transform(s => s.toUpperCase())),
userId: z.string().brand<"UserId">(),
immutable: z.string().readonly(),
});

Custom Validation & Transformation

model CustomValidation {
id String @id @default(cuid())
/// @zod.refine((val) => val.length > 0, { message: "Cannot be empty" })
content String
/// @zod.transform((val) => val.trim().toLowerCase())
slug String
/// @zod.enum(["admin", "user", "guest"])
role String
}

Special Field Types

model SpecialTypes {
id String @id @default(cuid())
/// @zod.json()
metadata Json
/// @zod.custom({ "name": "John", "age": 30, "active": true })
profile Json
}

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();

Import Features

  • 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 are detected and omitted
  • Field-level imports are merged with model-level imports
  • Duplicate statements are emitted once

Model-level imports

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
}

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.

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 in @zod.custom()

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

Chaining Support

All methods can be chained together:

model ChainedValidations {
id String @id @default(cuid())
/// @zod.email().max(100).toLowerCase()
email String @unique
/// @zod.nanoid().min(21)
publicId String
/// @zod.min(1).max(50).trim().regex(/^[A-Za-z\s]+$/)
name String
/// @zod.positive().int().multipleOf(5)
score Int
}

Generated schema (Zod v4):

export const ChainedValidationsCreateInputSchema = z.object({
email: z.email().max(100).toLowerCase(),
publicId: z.nanoid().min(21),
name: z.string().min(1).max(50).trim().regex(/^[A-Za-z\s]+$/),
score: z.number().int().positive().multipleOf(5),
});

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(),
});

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

Complete Method Reference

String Methods

MethodParametersDescription
@zod.min(n)number, optional error messageMinimum string length
@zod.max(n)number, optional error messageMaximum string length
@zod.length(n)number, optional error messageExact string length
@zod.email()optional error message/configEmail validation
@zod.url()optional error message/configURL validation
@zod.uuid()optional error message/configUUID validation
@zod.regex()pattern, optional error messageRegular expression validation
@zod.includes()substringString must contain substring
@zod.startsWith()prefixString must start with prefix
@zod.endsWith()suffixString must end with suffix
@zod.trim()noneRemove leading/trailing whitespace
@zod.toLowerCase()noneConvert to lowercase
@zod.toUpperCase()noneConvert to uppercase
@zod.datetime()optional error message/configISO datetime validation

Zod v4 String Format Methods

MethodDescriptionZod v4 OutputZod v3 Fallback
@zod.httpUrl()HTTP/HTTPS URL validationz.httpUrl()z.string()
@zod.hostname()Hostname validationz.hostname()z.string()
@zod.nanoid()Nanoid validationz.nanoid()z.string()
@zod.cuid()CUID validationz.cuid()z.string()
@zod.cuid2()CUID v2 validationz.cuid2()z.string()
@zod.ulid()ULID validationz.ulid()z.string()
@zod.base64()Base64 validationz.base64()z.string()
@zod.base64url()Base64URL validationz.base64url()z.string()
@zod.hex()Hexadecimal validationz.hex()z.string()
@zod.jwt()JWT token validationz.jwt()z.string()
@zod.hash(algo)Hash validationz.hash("sha256")z.string()
@zod.ipv4()IPv4 address validationz.ipv4()z.string()
@zod.ipv6()IPv6 address validationz.ipv6()z.string()
@zod.cidrv4()CIDR v4 validationz.cidrv4()z.string()
@zod.cidrv6()CIDR v6 validationz.cidrv6()z.string()
@zod.emoji()Single emoji validationz.emoji()z.string()
@zod.isoDate()ISO date validationz.iso.date()z.string()
@zod.isoTime()ISO time validationz.iso.time()z.string()
@zod.isoDatetime()ISO datetime validationz.iso.datetime()z.string()
@zod.isoDuration()ISO duration validationz.iso.duration()z.string()

Number Methods

MethodParametersField TypesDescription
@zod.min(n)number, optional error messageInt, Float, BigIntMinimum value
@zod.max(n)number, optional error messageInt, Float, BigIntMaximum value
@zod.int()noneInt, FloatInteger validation
@zod.positive()optional error messageInt, Float, BigIntPositive number (> 0)
@zod.negative()noneInt, Float, BigIntNegative number (< 0)
@zod.nonnegative()noneInt, Float, BigIntNon-negative number (≥ 0)
@zod.nonpositive()noneInt, Float, BigIntNon-positive number (≤ 0)
@zod.finite()noneFloatFinite number
@zod.safe()noneInt, FloatSafe integer
@zod.multipleOf(n)number, optional error messageInt, FloatMultiple of validation

Array Methods

MethodParametersDescription
@zod.min(n)numberMinimum array length
@zod.max(n)numberMaximum array length
@zod.length(n)numberExact array length
@zod.nonempty()noneNon-empty array

Date Methods

MethodParametersDescription
@zod.min(date)DateMinimum date
@zod.max(date)DateMaximum date

Field Modifiers

MethodParametersDescription
@zod.optional()noneMake field optional
@zod.nullable()noneMake field nullable
@zod.nullish()noneMake field nullish (null or undefined)
@zod.default(value)any valueSet default value

Custom Validation

MethodParametersDescription
@zod.refine(fn)functionCustom validation function
@zod.transform(fn)functionValue transformation
@zod.enum(options)arrayEnum validation

Special Types

MethodParametersField TypesDescription
@zod.json()noneJsonJSON validation
@zod.object()noneAnyObject validation
@zod.array()optional schemaAnyArray validation
@zod.custom(schema)object literalJsonStructured object schema

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)
  • Booleans: @zod.optional()z.string().optional()
  • 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('.'))