📋 Cheat Sheets

Zod Cheat Sheet — Schema Validation Patterns for TypeScript


Click any item to expand the explanation and examples.

📦 Primitives

Basic types basics
import { z } from 'zod';

z.string() z.number() z.bigint() z.boolean() z.date() z.undefined() z.null() z.void() z.any() z.unknown() z.never()

// Literals z.literal(‘hello’) z.literal(42) z.literal(true)

String validations string
z.string().min(1)                // Non-empty
z.string().min(3, 'Too short')   // Custom message
z.string().max(255)
z.string().length(10)
z.string().email()
z.string().url()
z.string().uuid()
z.string().cuid()
z.string().regex(/^[a-z]+$/)
z.string().startsWith('https://')
z.string().endsWith('.com')
z.string().trim()                // Trims whitespace
z.string().toLowerCase()
z.string().toUpperCase()
z.string().datetime()            // ISO 8601
z.string().ip()                  // IPv4 or IPv6
Number validations number
z.number().int()
z.number().positive()
z.number().nonnegative()
z.number().negative()
z.number().min(0)
z.number().max(100)
z.number().multipleOf(5)
z.number().finite()
z.number().safe()    // Number.MIN_SAFE_INTEGER to MAX

📋 Objects

Object schemas object
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().int().positive(),
});

// Parse (throws on error) const user = UserSchema.parse(data);

// Safe parse (returns result object) const result = UserSchema.safeParse(data); if (result.success) { console.log(result.data); } else { console.log(result.error.issues); }

// Infer TypeScript type type User = z.infer<typeof UserSchema>; // { name: string; email: string; age: number }

Optional, nullable, defaults object
z.object({
  name: z.string(),
  bio: z.string().optional(),          // string | undefined
  avatar: z.string().nullable(),       // string | null
  role: z.string().default('user'),    // defaults to 'user'
  tags: z.array(z.string()).default([]),
});

// .nullish() = optional + nullable (string | null | undefined)

Object manipulation object
const User = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  password: z.string(),
});

// Pick specific fields const PublicUser = User.pick({ name: true, email: true });

// Omit fields const CreateUser = User.omit({ id: true });

// Make all fields optional const UpdateUser = User.partial();

// Make specific fields optional const PatchUser = User.partial({ name: true, email: true });

// Extend const AdminUser = User.extend({ role: z.literal(‘admin’), permissions: z.array(z.string()), });

// Merge two schemas const Combined = SchemaA.merge(SchemaB);

// Strip unknown keys (default behavior) User.parse(dataWithExtraKeys); // extra keys removed

// Allow unknown keys User.passthrough().parse(data); // extra keys kept

// Reject unknown keys User.strict().parse(data); // throws if extra keys

📚 Arrays, Tuples, Unions

Arrays collection
z.array(z.string())              // string[]
z.array(z.number()).min(1)       // At least 1 item
z.array(z.number()).max(10)      // At most 10
z.array(z.number()).length(3)    // Exactly 3
z.array(z.number()).nonempty()   // At least 1 (narrows type)

// Shorthand z.string().array() // Same as z.array(z.string())

Unions, enums, discriminated unions collection
// Union
z.union([z.string(), z.number()])
z.string().or(z.number())        // Shorthand

// Enum z.enum([‘admin’, ‘user’, ‘guest’]) const RoleEnum = z.enum([‘admin’, ‘user’, ‘guest’]); type Role = z.infer<typeof RoleEnum>; // ‘admin’ | ‘user’ | ‘guest’ RoleEnum.enum.admin // ‘admin’ (autocomplete!)

// Native enum enum Direction { Up, Down } z.nativeEnum(Direction)

// Discriminated union (better error messages) const Shape = z.discriminatedUnion(‘type’, [ z.object({ type: z.literal(‘circle’), radius: z.number() }), z.object({ type: z.literal(‘rect’), width: z.number(), height: z.number() }), ]);

// Tuple z.tuple([z.string(), z.number()]) // [string, number]

// Record z.record(z.string(), z.number()) // { [key: string]: number }

🔄 Transforms & Pipes

Transform, refine, preprocess transform
// Transform output
const trimmed = z.string().transform(s => s.trim());
const toNumber = z.string().transform(Number);

// Coerce (parse input to type) z.coerce.number() // “42” → 42 z.coerce.boolean() // “true” → true z.coerce.date() // “2026-01-01” → Date z.coerce.string() // 42 → “42”

// Custom validation with refine const password = z.string() .min(8) .refine(s => /[A-Z]/.test(s), ‘Needs uppercase’) .refine(s => /[0-9]/.test(s), ‘Needs number’);

// Superrefine (multiple errors) const schema = z.object({ password: z.string(), confirm: z.string(), }).superRefine((data, ctx) => { if (data.password !== data.confirm) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: ‘Passwords must match’, path: [‘confirm’], }); } });

// Preprocess (transform before validation) z.preprocess( (val) => (typeof val === ‘string’ ? val.trim() : val), z.string().min(1) );

⚡ Common Patterns

API request/response validation pattern
// Define schemas
const CreateUserInput = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  password: z.string().min(8),
});

const UserResponse = z.object({ id: z.number(), email: z.string(), name: z.string(), createdAt: z.coerce.date(), });

// Use in API route export async function POST(req: Request) { const body = await req.json(); const input = CreateUserInput.parse(body); // input is fully typed: { email: string, name: string, password: string } const user = await createUser(input); return Response.json(UserResponse.parse(user)); }

Environment variable validation pattern
const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().default(3000),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});

export const env = envSchema.parse(process.env);

Validates all env vars at startup. If anything is missing, you get a clear error instead of a runtime crash later.

Form validation pattern
const ContactForm = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email'),
  message: z.string().min(10, 'Message too short').max(1000),
});

// Get formatted errors const result = ContactForm.safeParse(formData); if (!result.success) { const errors = result.error.flatten(); // errors.fieldErrors = { name: [’…’], email: [’…’] } }