Zod
Zod Validation
Zod Validation Schema Basics import { z } from 'zod'; // Primitives z.string() z.number() z.boolean() z.date() z.undefined() z.null() z.unknown() z.any() z.void…
Zod Validation
Schema Basics
import { z } from 'zod';
// Primitives
z.string()
z.number()
z.boolean()
z.date()
z.undefined()
z.null()
z.unknown()
z.any()
z.void()
z.never()
// String refinements
z.string().min(1).max(255)
z.string().email()
z.string().url()
z.string().uuid()
z.string().regex(/^[a-z]+$/)
z.string().startsWith('https://')
z.string().trim() // transforms: trims whitespace
z.string().toLowerCase() // transforms: lowercases
z.string().optional() // string | undefined
z.string().nullable() // string | null
z.string().nullish() // string | null | undefined
z.string().default('anonymous') // provides default value
// Number refinements
z.number().int()
z.number().positive()
z.number().min(0).max(100)
z.number().finite()
z.number().safe() // within Number.MIN/MAX_SAFE_INTEGER
// Literals & enums
z.literal('hello')
z.literal(42)
z.enum(['pending', 'active', 'inactive'])
z.nativeEnum(MyTsEnum)
// Arrays
z.array(z.string())
z.array(z.string()).min(1).max(10)
z.array(z.string()).nonempty()
z.tuple([z.string(), z.number()]) // fixed-length typed tupleObjects & Composition
// Object schema
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email().toLowerCase(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'user']).default('user'),
createdAt: z.date().or(z.string().transform(s => new Date(s))),
settings: z.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true),
}).optional(),
});
type User = z.infer<typeof UserSchema>; // TypeScript type from schema
// Object methods
UserSchema.partial() // all fields optional
UserSchema.required() // all fields required
UserSchema.pick({ id: true, name: true })
UserSchema.omit({ createdAt: true })
UserSchema.extend({ newField: z.string() })
UserSchema.merge(OtherSchema)
UserSchema.strip() // remove unknown keys (default)
UserSchema.passthrough() // allow unknown keys
UserSchema.strict() // throw on unknown keys
// Union & intersection
const StringOrNumber = z.union([z.string(), z.number()]);
const StringOrNumber2 = z.string().or(z.number()); // shorthand
const AdminUser = UserSchema.and(z.object({ permissions: z.array(z.string()) }));
// Discriminated union
const EventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
z.object({ type: z.literal('keypress'), key: z.string() }),
]);
// Record (key-value map)
const Config = z.record(z.string(), z.unknown());
const NameMap = z.record(z.string(), z.string()); // { [key: string]: string }Parsing & Error Handling
// parse — throws ZodError on invalid data
const user = UserSchema.parse(rawData);
// safeParse — returns { success, data } or { success, error }
const result = UserSchema.safeParse(rawData);
if (result.success) {
console.log(result.data.email); // fully typed
} else {
console.log(result.error.issues); // ZodIssue[]
console.log(result.error.format()); // nested error object
console.log(result.error.flatten()); // { formErrors: [], fieldErrors: {} }
}
// Custom error messages
z.string({ required_error: 'Name is required' })
.min(1, 'Name cannot be empty')
.max(100, { message: 'Name too long' });
// Custom validation with refine
const PasswordSchema = z.object({
password: z.string().min(8),
confirm: z.string(),
}).refine(data => data.password === data.confirm, {
message: 'Passwords do not match',
path: ['confirm'], // which field to attach error to
});
// superRefine — multiple errors
const Schema = z.string().superRefine((val, ctx) => {
if (!/[A-Z]/.test(val)) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Need uppercase' });
}
if (!/[0-9]/.test(val)) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Need digit' });
}
});
// Transform — validate then transform
const MoneySchema = z.string()
.regex(/^\d+(\.\d{2})?$/, 'Invalid amount')
.transform(s => parseFloat(s));
// MoneySchema.parse('10.50') → 10.5 (number)