7.3 KiB
7.3 KiB
Comprehensive Zod Schemas Guide
Complete reference for all Zod schema types and patterns.
Primitives
// String
z.string()
z.string().min(3, "Min 3 characters")
z.string().max(100, "Max 100 characters")
z.string().length(10, "Exactly 10 characters")
z.string().email("Invalid email")
z.string().url("Invalid URL")
z.string().uuid("Invalid UUID")
z.string().regex(/pattern/, "Does not match pattern")
z.string().trim() // Trim whitespace
z.string().toLowerCase() //Convert to lowercase
z.string().toUpperCase() // Convert to uppercase
// Number
z.number()
z.number().int("Must be integer")
z.number().positive("Must be positive")
z.number().negative("Must be negative")
z.number().min(0, "Min is 0")
z.number().max(100, "Max is 100")
z.number().multipleOf(5, "Must be multiple of 5")
z.number().finite() // No Infinity or NaN
z.number().safe() // Within JS safe integer range
// Boolean
z.boolean()
// Date
z.date()
z.date().min(new Date("2020-01-01"), "Too old")
z.date().max(new Date(), "Cannot be in future")
// BigInt
z.bigint()
Objects
// Basic object
const userSchema = z.object({
name: z.string(),
age: z.number(),
})
// Nested object
const profileSchema = z.object({
user: userSchema,
address: z.object({
street: z.string(),
city: z.string(),
}),
})
// Partial (all fields optional)
const partialUserSchema = userSchema.partial()
// Deep Partial (recursively optional)
const deepPartialSchema = profileSchema.deepPartial()
// Pick specific fields
const nameOnlySchema = userSchema.pick({ name: true })
// Omit specific fields
const withoutAgeSchema = userSchema.omit({ age: true })
// Merge objects
const extendedUserSchema = userSchema.merge(z.object({
email: z.string().email(),
}))
// Passthrough (allow extra fields)
const passthroughSchema = userSchema.passthrough()
// Strict (no extra fields)
const strictSchema = userSchema.strict()
// Catchall (type for extra fields)
const catchallSchema = userSchema.catchall(z.string())
Arrays
// Array of strings
z.array(z.string())
// With length constraints
z.array(z.string()).min(1, "At least one item required")
z.array(z.string()).max(10, "Max 10 items")
z.array(z.string()).length(5, "Exactly 5 items")
z.array(z.string()).nonempty("Array cannot be empty")
// Array of objects
z.array(z.object({
name: z.string(),
age: z.number(),
}))
Tuples
// Fixed-length array with specific types
z.tuple([z.string(), z.number(), z.boolean()])
// With rest
z.tuple([z.string(), z.number()]).rest(z.boolean())
Enums and Literals
// Enum
z.enum(['red', 'green', 'blue'])
// Native enum
enum Color { Red, Green, Blue }
z.nativeEnum(Color)
// Literal
z.literal('hello')
z.literal(42)
z.literal(true)
Unions and Discriminated Unions
// Union
z.union([z.string(), z.number()])
// Discriminated union (recommended for better errors)
z.discriminatedUnion('type', [
z.object({ type: z.literal('user'), name: z.string() }),
z.object({ type: z.literal('admin'), permissions: z.array(z.string()) }),
])
Optional and Nullable
// Optional (value | undefined)
z.string().optional()
z.optional(z.string()) // Same as above
// Nullable (value | null)
z.string().nullable()
z.nullable(z.string()) // Same as above
// Nullish (value | null | undefined)
z.string().nullish()
Default Values
z.string().default('default value')
z.number().default(0)
z.boolean().default(false)
z.array(z.string()).default([])
Refinements (Custom Validation)
// Basic refinement
z.string().refine((val) => val.length > 5, {
message: "String must be longer than 5 characters",
})
// With custom path
z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
})
// Multiple refinements
z.string()
.refine((val) => val.length >= 8, "Min 8 characters")
.refine((val) => /[A-Z]/.test(val), "Must contain uppercase")
.refine((val) => /[0-9]/.test(val), "Must contain number")
// Async refinement
z.string().refine(async (val) => {
const available = await checkAvailability(val)
return available
}, "Already taken")
Transforms
// String to number
z.string().transform((val) => parseInt(val, 10))
// Trim whitespace
z.string().transform((val) => val.trim())
// Parse date
z.string().transform((val) => new Date(val))
// Chain transform and refine
z.string()
.transform((val) => parseInt(val, 10))
.refine((val) => !isNaN(val), "Must be a number")
Preprocess
// Process before validation
z.preprocess(
(val) => (val === '' ? undefined : val),
z.string().optional()
)
// Convert to number
z.preprocess(
(val) => Number(val),
z.number()
)
Intersections
const baseUser = z.object({ name: z.string() })
const withEmail = z.object({ email: z.string().email() })
// Intersection (combines both)
const userWithEmail = baseUser.and(withEmail)
// OR
const userWithEmail = z.intersection(baseUser, withEmail)
Records and Maps
// Record (object with dynamic keys)
z.record(z.string()) // { [key: string]: string }
z.record(z.string(), z.number()) // { [key: string]: number }
// Map
z.map(z.string(), z.number())
Sets
z.set(z.string())
z.set(z.number()).min(1, "At least one item")
z.set(z.string()).max(10, "Max 10 items")
Promises
z.promise(z.string())
z.promise(z.object({ data: z.string() }))
Custom Error Messages
// Field-level
z.string({ required_error: "Name is required" })
z.number({ invalid_type_error: "Must be a number" })
// Validation-level
z.string().min(3, { message: "Min 3 characters" })
z.string().email({ message: "Invalid email format" })
// Custom error map
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_type) {
if (issue.expected === "string") {
return { message: "Please enter text" }
}
}
return { message: ctx.defaultError }
}
z.setErrorMap(customErrorMap)
Type Inference
const userSchema = z.object({
name: z.string(),
age: z.number(),
})
// Infer TypeScript type
type User = z.infer<typeof userSchema>
// Result: { name: string; age: number }
// Input type (before transforms)
type UserInput = z.input<typeof transformSchema>
// Output type (after transforms)
type UserOutput = z.output<typeof transformSchema>
Parsing Methods
// .parse() - throws on error
const result = schema.parse(data)
// .safeParse() - returns result object
const result = schema.safeParse(data)
if (result.success) {
console.log(result.data)
} else {
console.error(result.error)
}
// .parseAsync() - async validation
const result = await schema.parseAsync(data)
// .safeParseAsync() - async with result object
const result = await schema.safeParseAsync(data)
Error Handling
try {
schema.parse(data)
} catch (error) {
if (error instanceof z.ZodError) {
// Formatted errors
console.log(error.format())
// Flattened errors (for forms)
console.log(error.flatten())
// Individual issues
console.log(error.issues)
}
}
Official Docs: https://zod.dev