with one click
zod
Zod v4 schema validation. Use for zod, schema, validation, parse, safeParse, infer, coerce, transform, refine, z.object, z.string, z.email, z.url
Menu
Zod v4 schema validation. Use for zod, schema, validation, parse, safeParse, infer, coerce, transform, refine, z.object, z.string, z.email, z.url
| 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 |
This project uses Zod v4. Note the syntax differences from v3.
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);
}
z.string();
z.number();
z.boolean();
z.bigint();
z.date();
z.undefined();
z.null();
z.void();
z.any();
z.unknown();
z.never();
Zod v4 uses top-level functions instead of method chaining:
// Zod v4 (use these)
z.email();
z.url();
z.uuid();
z.cuid();
z.cuid2();
z.ulid();
z.nanoid();
z.ipv4();
z.ipv6();
z.base64();
z.emoji();
// Deprecated (don't use)
z.string().email();
z.string().url();
z.string().uuid();
z.iso.date(); // "2024-01-15"
z.iso.time(); // "14:30:00"
z.iso.datetime(); // "2024-01-15T14:30:00Z"
z.iso.duration(); // "P3Y6M4DT12H30M5S"
z.string().min(1); // Non-empty
z.string().max(100); // Max length
z.string().length(5); // Exact length
z.string().regex(/^[a-z]+$/); // Pattern
z.string().trim(); // Trim whitespace
z.string().toLowerCase(); // Lowercase
z.string().toUpperCase(); // Uppercase
z.string().startsWith('https');
z.string().endsWith('.com');
z.string().includes('@');
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(); // Safe integer range
const User = z.object({
id: z.string(),
email: z.email(),
age: z.number().optional(),
});
type User = z.infer<typeof User>;
// Modifiers
User.partial(); // All optional
User.required(); // All required
User.pick({ id: true, email: true });
User.omit({ age: true });
User.extend({ role: z.string() });
User.merge(OtherSchema);
User.passthrough(); // Allow extra keys
User.strict(); // Reject extra keys
User.strip(); // Remove extra keys
z.array(z.string());
z.array(z.number()).min(1); // Non-empty
z.array(z.number()).max(10);
z.array(z.number()).length(5);
z.array(z.number()).nonempty(); // Non-empty (typed)
// Tuple
z.tuple([z.string(), z.number()]);
z.tuple([z.string(), z.number()]).rest(z.boolean());
// Native enum
z.enum(['admin', 'user', 'guest']);
// Union
z.union([z.string(), z.number()]);
z.string().or(z.number()); // Shorthand
// Discriminated union (better errors)
z.discriminatedUnion('type', [
z.object({ type: z.literal('email'), email: z.email() }),
z.object({ type: z.literal('phone'), phone: z.string() }),
]);
// Literal
z.literal('active');
z.literal(42);
z.literal(true);
// Record (string keys by default)
z.record(z.number()); // { [key: string]: number }
z.record(z.string(), z.number());
// Enum keys (v4: all keys required)
z.record(z.enum(['a', 'b']), z.number()); // { a: number, b: number }
z.partialRecord(z.enum(['a', 'b']), z.number()); // { a?: number, b?: number }
// Map
z.map(z.string(), z.number());
z.string().optional(); // string | undefined
z.string().nullable(); // string | null
z.string().nullish(); // string | null | undefined
// Defaults
z.string().default('hello');
z.number().default(0);
// Catch (use default on parse error)
z.string().catch('fallback');
z.coerce.string(); // Converts to string
z.coerce.number(); // Converts to number
z.coerce.boolean(); // Falsy -> false, truthy -> true
z.coerce.date(); // Converts to Date
z.coerce.bigint(); // Converts to BigInt
// Env-style boolean parsing
z.stringbool();
// Recognized values:
// true: "true", "1", "yes", "on", "y", "enabled"
// false: "false", "0", "no", "off", "n", "disabled"
// Custom values
z.stringbool({
truthy: ['yes', 'true'],
falsy: ['no', 'false'],
});
// Transform output type
z.string().transform((val) => val.length); // string -> number
z.string().transform((val) => parseInt(val, 10));
// Overwrite (same type, introspectable)
z.number().overwrite((val) => val * 2);
z.string().overwrite((val) => val.trim());
// Simple refinement
z.string().refine((val) => val.includes('@'), {
message: 'Must contain @',
});
// Multiple refinements
z.string()
.refine((val) => val.length > 0, 'Required')
.refine((val) => val.includes('@'), 'Must contain @');
// SuperRefine (custom errors)
z.string().superRefine((val, ctx) => {
if (!val.includes('@')) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Must contain @',
});
}
});
const schema = z.string();
// Throws on error
schema.parse('hello'); // 'hello'
schema.parse(123); // throws ZodError
// Returns result object
schema.safeParse('hello'); // { success: true, data: 'hello' }
schema.safeParse(123); // { success: false, error: ZodError }
// Async
await schema.parseAsync('hello');
await schema.safeParseAsync('hello');
const UserSchema = z.object({
id: z.string(),
email: z.email(),
});
type User = z.infer<typeof UserSchema>;
// { id: string; email: string }
// Input vs Output types (with transforms)
type UserInput = z.input<typeof UserSchema>;
type UserOutput = z.output<typeof UserSchema>;
const LoginSchema = z.object({
email: z.email(),
password: z.string().min(8),
rememberMe: z.boolean().default(false),
});
const ApiResponse = z.object({
data: z.unknown(),
error: z.string().nullable(),
status: z.enum(['success', 'error']),
});
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']),
});
| 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 |
superRefine for multi-field validationExplore agent to find existing schemasGit workflow for branches, commits, and PRs. Use for commit, branch, pr, pull request, conventional, push, feat, fix, chore, merge, rebase
Storybook stories and interaction tests. Use for story, stories, storybook, chromatic, visual test, interaction test, play function, component test
TypeScript and unicorn linting patterns. Use for typescript, array, for-of, reduce, forEach, throw, catch, modern js, es modules, string, number, error handling
Better Auth authentication. Use for auth, login, logout, session, user, signup, register, protect, middleware, password, oauth, social
Drizzle ORM + PostgreSQL database layer. Use for db, database, query, schema, table, migrate, sql, postgres, drizzle, model, relation
React error boundaries + fallback UIs. Use for error, catch, boundary, fallback, recovery, crash, ErrorBoundary, errorComponent, reset