| name | zod |
| description | Zod v4 schema validation. Use for zod, schema, validation, parse, safeParse, infer, coerce, transform, refine, z.object, z.string, z.email, z.url |
Zod Schema Validation (v4)
This project uses Zod v4. Note the syntax differences from v3.
Quick Start
import { z } from 'zod';
const UserSchema = z.object({
email: z.email(),
name: z.string().min(1),
age: z.number().optional(),
});
type User = z.infer<typeof UserSchema>;
const result = UserSchema.safeParse(input);
if (result.success) {
console.log(result.data);
} else {
console.log(result.error);
}
Primitives
z.string();
z.number();
z.boolean();
z.bigint();
z.date();
z.undefined();
z.null();
z.void();
z.any();
z.unknown();
z.never();
String Formats (v4 Top-Level API)
Zod v4 uses top-level functions instead of method chaining:
z.email();
z.url();
z.uuid();
z.cuid();
z.cuid2();
z.ulid();
z.nanoid();
z.ipv4();
z.ipv6();
z.base64();
z.emoji();
z.string().email();
z.string().url();
z.string().uuid();
ISO Date/Time Formats
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();
String Constraints
z.string().min(1);
z.string().max(100);
z.string().length(5);
z.string().regex(/^[a-z]+$/);
z.string().trim();
z.string().toLowerCase();
z.string().toUpperCase();
z.string().startsWith('https');
z.string().endsWith('.com');
z.string().includes('@');
Number Constraints
z.number().min(0);
z.number().max(100);
z.number().int();
z.number().positive();
z.number().negative();
z.number().nonnegative();
z.number().nonpositive();
z.number().multipleOf(5);
z.number().finite();
z.number().safe();
Objects
const User = z.object({
id: z.string(),
email: z.email(),
age: z.number().optional(),
});
type User = z.infer<typeof User>;
User.partial();
User.required();
User.pick({ id: true, email: true });
User.omit({ age: true });
User.extend({ role: z.string() });
User.merge(OtherSchema);
User.passthrough();
User.strict();
User.strip();
Arrays
z.array(z.string());
z.array(z.number()).min(1);
z.array(z.number()).max(10);
z.array(z.number()).length(5);
z.array(z.number()).nonempty();
z.tuple([z.string(), z.number()]);
z.tuple([z.string(), z.number()]).rest(z.boolean());
Enums & Unions
z.enum(['admin', 'user', 'guest']);
z.union([z.string(), z.number()]);
z.string().or(z.number());
z.discriminatedUnion('type', [
z.object({ type: z.literal('email'), email: z.email() }),
z.object({ type: z.literal('phone'), phone: z.string() }),
]);
z.literal('active');
z.literal(42);
z.literal(true);
Records & Maps
z.record(z.number());
z.record(z.string(), z.number());
z.record(z.enum(['a', 'b']), z.number());
z.partialRecord(z.enum(['a', 'b']), z.number());
z.map(z.string(), z.number());
Optional & Nullable
z.string().optional();
z.string().nullable();
z.string().nullish();
z.string().default('hello');
z.number().default(0);
z.string().catch('fallback');
Coercion
z.coerce.string();
z.coerce.number();
z.coerce.boolean();
z.coerce.date();
z.coerce.bigint();
String to Boolean (v4)
z.stringbool();
z.stringbool({
truthy: ['yes', 'true'],
falsy: ['no', 'false'],
});
Transforms
z.string().transform((val) => val.length);
z.string().transform((val) => parseInt(val, 10));
z.number().overwrite((val) => val * 2);
z.string().overwrite((val) => val.trim());
Refinements
z.string().refine((val) => val.includes('@'), {
message: 'Must contain @',
});
z.string()
.refine((val) => val.length > 0, 'Required')
.refine((val) => val.includes('@'), 'Must contain @');
z.string().superRefine((val, ctx) => {
if (!val.includes('@')) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Must contain @',
});
}
});
Parsing
const schema = z.string();
schema.parse('hello');
schema.parse(123);
schema.safeParse('hello');
schema.safeParse(123);
await schema.parseAsync('hello');
await schema.safeParseAsync('hello');
Type Inference
const UserSchema = z.object({
id: z.string(),
email: z.email(),
});
type User = z.infer<typeof UserSchema>;
type UserInput = z.input<typeof UserSchema>;
type UserOutput = z.output<typeof UserSchema>;
Common Patterns
Form Validation
const LoginSchema = z.object({
email: z.email(),
password: z.string().min(8),
rememberMe: z.boolean().default(false),
});
API Response
const ApiResponse = z.object({
data: z.unknown(),
error: z.string().nullable(),
status: z.enum(['success', 'error']),
});
Environment Variables
const EnvSchema = z.object({
DATABASE_URL: z.url(),
PORT: z.coerce.number().default(3000),
DEBUG: z.stringbool().default(false),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
Common Mistakes
| Mistake | Correct Pattern |
|---|
z.string().email() | z.email() (v4 top-level) |
z.string().url() | z.url() (v4 top-level) |
Using parse without catch | Use safeParse for error handling |
Forgetting .optional() | Add when field may be undefined |
Using any for unknown data | Use z.unknown() instead |
Delegation
- Schema design: Ask user for field requirements
- Complex validation: Use
superRefine for multi-field validation
- Pattern discovery: Use
Explore agent to find existing schemas