Overview Install Files SecuritySummary This source did not publish a separate summary. Review SKILL.md before using the skill.
Zod: TypeScript-First Schema Validation
Overview
Zod is a TypeScript-first validation library that enables developers to define schemas for validating data at runtime while automatically inferring static TypeScript types. With zero dependencies and a compact core (~5kb gzipped; ~1.9kb for zod/mini), Zod provides immutable, composable validation with comprehensive error handling.
Installation
bun add zod
# or
npm install zod
# or
pnpm add zod
# or
yarn add zod
Requirements :
TypeScript v5.5+ with "strict": true in tsconfig.json
Zod 4.x (4.4.x recommended; z.codec() requires 4.1+)
Important : This skill documents Zod 4.x features. The following APIs require Zod 4 and are NOT available in Zod 3.x:
z.codec() - Bidirectional transformations
z.iso.date(), z.iso.time(), z.iso.datetime(), z.iso.duration() - ISO format validators
z.toJSONSchema() - JSON Schema generation
z.treeifyError(), z.prettifyError(), z.flattenError() - New error formatting helpers
.meta() - Enhanced metadata (Zod 3.x only has .describe())
Unified error parameter - Replaces message, invalid_type_error, required_error, errorMap
.check() - Low-level multi-issue validation (composes check factories like z.minLength(3))
z.file(), z.json(), z.stringbool(), z.xor(), z.templateLiteral() - New schema types
z.exactOptional(), .prefault(), .safeExtend(), z.fromJSONSchema() - New utilitiesImport paths (all ship in the zod package):
import { z } from "zod" — standard (v4 since 4.0)
import { z } from "zod/v4" — pin for libraries supporting both v3 (3.25+) and v4 users
import { z } from "zod/mini" — functional, tree-shakable API (~1.9kb; checks via .check(z.minLength(3)))
import * as core from "zod/v4/core" — low-level $-prefixed internals for library authors
Migrating from Zod v3 to v4 Load references/migration-guide.md for complete v3 to v4 migration documentation.
Quick Summary Zod v4 introduces breaking changes for better performance:
Error customization : Use unified error parameter (replaces message, invalid_type_error, required_error)
Number validation : Stricter - rejects Infinity and unsafe integers
String formats : Prefer top-level functions (z.email() instead of z.string().email(); the method forms still work but are deprecated)
Object defaults : Applied even in optional fields
Deprecated APIs : Use .extend() (not .merge()), z.treeifyError() (not error.format())
Function validation : Use .implement() method
UUID validation : Stricter RFC 9562/4122 compliance
→ Load references/migration-guide.md for: Complete breaking changes, migration checklist, gradual migration strategy, rollback instructions
Core Concepts
Basic Usage Pattern import { z } from "zod";
// Define schema
const UserSchema = z.object({
username: z.string(),
age: z.number().int().positive(),
email: z.email(),
});
// Infer TypeScript type
type User = z.infer<typeof UserSchema>;
// Validate data (throws on error)
const user = UserSchema.parse(data);
// Validate data (returns result object)
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // Typed!
} else {
console.error(result.error); // ZodError
}
Parsing Methods Use the appropriate parsing method based on error handling needs:
.parse(data) - Throws ZodError on invalid input; returns strongly-typed data on success
.safeParse(data) - Returns { success: true, data } or { success: false, error } (no exceptions)
.parseAsync(data) - For schemas with async refinements/transforms
.safeParseAsync(data) - Async version that doesn't throw
Best Practice : Use .safeParse() to avoid try-catch blocks and leverage discriminated unions.
Primitive Types
Strings z.string() // Basic string
z.string().min(5) // Minimum length
z.string().max(100) // Maximum length
z.string().length(10) // Exact length
z.email() // Email validation (top-level in v4)
z.url() // URL validation (top-level in v4)
z.uuid() // UUID (RFC 9562; use z.guid() for permissive)
z.uuidv4() // Version-specific UUIDs (also z.uuidv7)
z.httpUrl() // http/https URLs only (rejects ftp:// etc.)
z.e164() // Phone numbers (E.164 format)
z.string().regex(/^\d+$/) // Custom pattern
z.string().startsWith("pre") // Prefix check
z.string().endsWith("suf") // Suffix check
z.string().trim() // Auto-trim whitespace
z.string().toLowerCase() // Auto-lowercase
z.string().toUpperCase() // Auto-uppercase
// ISO formats (Zod 4+)
z.iso.date() // YYYY-MM-DD
z.iso.time() // HH:MM:SS
z.iso.datetime() // ISO 8601 datetime
z.iso.duration() // ISO 8601 duration
// Network formats
z.ipv4() // IPv4 address
z.ipv6() // IPv6 address
z.cidrv4() // IPv4 CIDR notation
z.cidrv6() // IPv6 CIDR notation
// Other formats
z.jwt() // JWT token
z.nanoid() // Nanoid
z.cuid() // CUID
z.cuid2() // CUID2
z.ulid() // ULID
z.base64() // Base64 encoded
z.hex() // Hexadecimal
z.hash("sha256") // Hash of algorithm-specific length
z.guid() // Permissive UUID/GUID (non-RFC)
z.hostname() // Hostname
z.mac() // MAC address
z.uuid({ version: "v4" }) // UUID with version constraint
z.email({ pattern: z.regexes.email }) // Email with explicit pattern
Numbers z.number() // Basic number
z.number().int() // Integer only
z.number().positive() // > 0
z.number().nonnegative() // >= 0
z.number().negative() // < 0
z.number().nonpositive() // <= 0
z.number().min(0) // Minimum value
z.number().max(100) // Maximum value
z.number().gt(0) // Greater than
z.number().gte(0) // Greater than or equal
z.number().lt(100) // Less than
z.number().lte(100) // Less than or equal
z.number().multipleOf(5) // Must be multiple of 5
z.int() // Shorthand for z.number().int()
z.int32() // 32-bit integer (also z.uint32, z.int64, z.uint64)
z.float32() // 32-bit float (also z.float64)
z.nan() // NaN value
Coercion (Type Conversion) z.coerce.string() // Convert to string
z.coerce.number() // Convert to number
z.coerce.boolean() // Convert to boolean
z.coerce.bigint() // Convert to bigint
z.coerce.date() // Convert to Date
// Example: Parse query parameters
const QuerySchema = z.object({
page: z.coerce.number().int().positive(),
limit: z.coerce.number().int().max(100).default(10),
});
// "?page=5&limit=20" -> { page: 5, limit: 20 }
Other Primitives z.boolean() // Boolean
z.date() // Date object
z.date().min(new Date("2020-01-01"))
z.date().max(new Date("2030-12-31"))
z.bigint() // BigInt
z.symbol() // Symbol
z.null() // Null
z.undefined() // Undefined
z.void() // Void (undefined)
// File validation (v4) — sizes are bytes
z.file().min(1).max(5 * 1024 * 1024).mime(["image/png"])
// JSON-representable values (v4) — validates the VALUE is JSON-serializable
// (recursive union of string | number | boolean | null | array | record)
z.json()
// Env-style boolean coercion: "true"/"yes"/"1" → true (v4)
z.stringbool({ truthy: ["true", "yes", "1"], falsy: ["false", "no", "0"] })
Complex Types
Objects const PersonSchema = z.object({
name: z.string(),
age: z.number(),
address: z.object({
street: z.string(),
city: z.string(),
country: z.string(),
}),
});
type Person = z.infer<typeof PersonSchema>;
// Object methods
PersonSchema.shape // Access shape
PersonSchema.keyof() // Get union of keys
PersonSchema.extend({ role: z.string() }) // Add fields
PersonSchema.safeExtend({ role: z.string() }) // Type-safe extend (inherits refinements)
PersonSchema.pick({ name: true }) // Pick specific fields
PersonSchema.omit({ age: true }) // Omit fields
PersonSchema.partial() // Make all fields optional
PersonSchema.partial({ age: true }) // Make specific fields optional
PersonSchema.required() // Make all fields required
// NOTE: .deepPartial() was REMOVED in v4 — build recursive partials manually
// (top-level z.deepPartial() landed post-4.4.3 and is not yet stable)
// Strict vs loose objects
z.strictObject({ ... }) // No extra keys allowed (throws)
z.object({ ... }) // Strips extra keys (default)
z.looseObject({ ... }) // Allows extra keys
z.object({ ... }).catchall(z.number()) // Validate (and keep) unknown keys
Arrays z.array(z.string()) // String array
z.array(z.number()).min(1) // At least 1 element
z.array(z.number()).max(10) // At most 10 elements
z.array(z.number()).length(5) // Exactly 5 elements
z.array(z.number()).nonempty() // At least 1 element
// Nested arrays
z.array(z.array(z.number())) // number[][]
Tuples z.tuple([z.string(), z.number()]) // [string, number]
z.tuple([z.string(), z.number()]).rest(z.boolean()) // [string, number, ...boolean[]]
Enums and Literals // Enum
const RoleEnum = z.enum(["admin", "user", "guest"]);
type Role = z.infer<typeof RoleEnum>; // "admin" | "user" | "guest"
// Literal values
z.literal("exact_value")
z.literal(42)
z.literal(true)
// Native TypeScript enum
enum Fruits {
Apple,
Banana,
}
z.nativeEnum(Fruits)
// Enum methods
RoleEnum.enum.admin // "admin"
RoleEnum.exclude(["guest"]) // Exclude values
RoleEnum.extract(["admin", "user"]) // Include only
Unions // Basic union
z.union([z.string(), z.number()])
// Discriminated union (better performance & type inference)
const ResponseSchema = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.any() }),
z.object({ status: z.literal("error"), message: z.string() }),
]);
type Response = z.infer<typeof ResponseSchema>;
// { status: "success", data: any } | { status: "error", message: string }
// Exclusive union (v4): must match exactly ONE branch
z.xor([z.object({ a: z.string() }), z.object({ b: z.number() })])
Intersections const BaseSchema = z.object({ id: z.string() });
const ExtendedSchema = z.object({ name: z.string() });
const Combined = z.intersection(BaseSchema, ExtendedSchema);
// Equivalent to: z.object({ id: z.string(), name: z.string() })
Records and Maps // Record: object with typed keys and values (BOTH args required in v4)
z.record(z.string(), z.number()) // { [key: string]: number }
z.record(z.enum(["a", "b"]), z.string()) // Exhaustive: both keys REQUIRED
// Partial record (enum keys optional)
z.partialRecord(z.enum(["a", "b"]), z.string())
// Map
z.map(z.string(), z.number()) // Map<string, number>
z.set(z.string()) // Set<string>
Advanced Patterns Load references/advanced-patterns.md for complete advanced validation and transformation patterns.
Quick Reference Refinements (custom validation):
z.string().refine((val) => val.length >= 8, "Too short");
z.object({ password, confirmPassword }).superRefine((data, ctx) => { /* ... */ });
Transformations (modify data):
z.string().transform((val) => val.trim());
z.string().pipe(z.coerce.number());
z.preprocess((v) => (typeof v === "string" ? v.trim() : v), z.string()); // Transform BEFORE parsing
z.custom<string>((v) => typeof v === "string", { error: "Not a string" }); // Fully custom schema
Multi-issue validation (.check(), v4):
z.string().check(z.minLength(3), z.startsWith("a")); // Compose check factories
z.string().check((payload) => {
if (payload.value === "forbidden") {
payload.issues.push({ code: "custom", message: "Not allowed", input: payload.value });
}
});
const UserRole = z.templateLiteral(["role-", z.string()]); // "role-admin", "role-editor", ...
Codecs (bidirectional transforms - NEW in v4.1):
const DateCodec = z.codec(
z.iso.datetime(),
z.date(),
{
decode: (str) => new Date(str),
encode: (date) => date.toISOString(),
}
);
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({ name: z.string(), subcategories: z.array(CategorySchema) })
);
z.string().optional() // string | undefined
z.string().nullable() // string | null
z.string().default("default") // Provides default if undefined
z.object({ ... }).readonly() // Readonly properties
z.string().brand<"UserId">() // Nominal typing
→ Load references/advanced-patterns.md for: Complete refinement patterns, async validation, codec examples, composable schemas, conditional validation, performance optimization
Error Handling Load references/error-handling.md for complete error formatting and customization guide.
Quick Reference Error Formatting Methods :
// For forms
const { fieldErrors } = z.flattenError(error);
// For nested data
const tree = z.treeifyError(error);
const nameError = tree.properties?.user?.properties?.name?.errors?.[0];
// For debugging
console.log(z.prettifyError(error));
Custom Error Messages (three levels):
// 1. Schema-level (highest priority)
z.string({ error: "Custom message" });
z.string().min(5, "Too short");
// 2. Per-parse level
schema.parse(data, { error: (issue) => "..." });
// 3. Global level
z.config({ customError: (issue) => "..." });
Localization (40+ languages):
z.config(z.locales.es()); // Spanish
z.config(z.locales.fr()); // French
→ Load references/error-handling.md for: Complete error formatting examples, custom error patterns, localization setup, error code reference
Type Inference Load references/type-inference.md for complete type inference and metadata documentation.
Quick Reference const UserSchema = z.object({ name: z.string() });
type User = z.infer<typeof UserSchema>; // { name: string }
Input vs Output (for transforms):
const TransformSchema = z.string().transform((s) => s.length);
type Input = z.input<typeof TransformSchema>; // string
type Output = z.output<typeof TransformSchema>; // number
const jsonSchema = z.toJSONSchema(UserSchema, {
target: "openapi-3.0",
// .meta() data from the global registry is included by default
});
// Add metadata
const EmailSchema = z.email().meta({
title: "Email Address",
description: "User's email address",
});
// Create custom registry
const formRegistry = z.registry<FormFieldMeta>();
→ Load references/type-inference.md for: Complete type inference patterns, JSON Schema options, metadata system, custom registries, brand types
Functions Validate function inputs and outputs with the v4 factory signature:
const AddFunction = z.function({
input: [z.number(), z.number()], // Arguments
output: z.number(), // Return type
});
// Implement typed function
const add = AddFunction.implement((a, b) => {
return a + b; // Type-checked!
});
// Async functions
const FetchFunction = z.function({
input: [z.string()],
output: z.object({ data: z.any() }),
});
const fetchJson = FetchFunction.implementAsync(async (url) => {
const response = await fetch(url);
return response.json();
});
Note : the v3 chaining style (z.function().args(...).returns(...)) was removed in v4.
Common Patterns
Environment Variables const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.url(),
PORT: z.coerce.number().int().positive().default(3000),
API_KEY: z.string().min(32),
});
// Validate on startup
const env = EnvSchema.parse(process.env);
// Now use typed env
console.log(env.PORT); // number
API Request Validation const CreateUserRequest = z.object({
username: z.string().min(3).max(20),
email: z.email(),
password: z.string().min(8),
age: z.number().int().positive().optional(),
});
// Express example
app.post("/users", async (req, res) => {
const result = CreateUserRequest.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
errors: z.flattenError(result.error).fieldErrors,
});
}
const user = await createUser(result.data);
res.json(user);
});
Form Validation const FormSchema = z.object({
firstName: z.string().min(1, "First name required"),
lastName: z.string().min(1, "Last name required"),
email: z.email({ error: "Invalid email" }),
age: z.coerce.number().int().min(18, "Must be 18+"),
agreeToTerms: z.literal(true, {
error: () => "Must accept terms",
}),
});
type FormData = z.infer<typeof FormSchema>;
Partial Updates const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.email(),
});
// For PATCH requests: make everything optional except id
const UpdateUserSchema = UserSchema.partial().required({ id: true });
type UpdateUser = z.infer<typeof UpdateUserSchema>;
// { id: string; name?: string; email?: string }
Composable Schemas // Base schemas
const TimestampSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
});
const AuthorSchema = z.object({
authorId: z.string(),
authorName: z.string(),
});
// Compose into larger schemas (.merge() is deprecated — use .extend with .shape)
const PostSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string(),
}).extend(TimestampSchema.shape).extend(AuthorSchema.shape);
Ecosystem Integration Load references/ecosystem-integrations.md for complete framework and tooling integration guide.
Quick Reference
eslint-plugin-zod-x - Enforces best practices
eslint-plugin-import-zod - Enforces import style
tRPC - End-to-end typesafe APIs
React Hook Form - Form validation (see react-hook-form-zod skill)
Prisma - Generate Zod from database models
NestJS - DTOs and validation pipes
orval - OpenAPI → Zod
Hey API - OpenAPI to TypeScript + Zod
kubb - API toolkit with codegen
→ Load references/ecosystem-integrations.md for: Setup instructions, integration examples, Hono middleware, Drizzle ORM patterns
Troubleshooting Load references/troubleshooting.md for complete troubleshooting guide, performance tips, and best practices.
Quick Reference
TypeScript strict mode required → Enable in tsconfig.json
Large bundle size → Use z.lazy() for code splitting
Slow async refinements → Cache or debounce
Circular dependencies → Use z.lazy()
Slow unions → Use z.discriminatedUnion()
Transform vs refine confusion → Use .refine() for validation, .transform() for modification
Use .discriminatedUnion() (5-10x faster than .union())
Cache schema instances
Use .safeParse() (avoids try-catch overhead)
Lazy load large schemas
Define schemas at module level
Use type inference (z.infer)
Add custom error messages
Validate at system boundaries
Compose small schemas
Document with .meta()
→ Load references/troubleshooting.md for: Detailed solutions, performance optimization, best practices, testing patterns
Quick Reference // Primitives
z.string(), z.number(), z.boolean(), z.date(), z.bigint()
// Collections
z.array(), z.tuple(), z.object(), z.record(), z.map(), z.set()
// Special types
z.enum(), z.union(), z.discriminatedUnion(), z.intersection()
z.literal(), z.any(), z.unknown(), z.never()
// Modifiers
.optional(), .nullable(), .nullish(), .default(), .prefault()
.catch(), .readonly(), .brand(), .exactOptional()
// Validation
.min(), .max(), .length(), .regex()
.refine(), .superRefine(), .check(z.minLength(3))
// Top-level formats (v4)
z.email(), z.uuid(), z.url(), z.iso.datetime(), z.file(), z.json()
// Transformation
.transform(), .pipe(), .codec(), z.preprocess(), z.custom()
// Composition helpers
z.strictObject(), z.looseObject(), z.xor(), z.templateLiteral()
// Parsing
.parse(), .safeParse(), .parseAsync(), .safeParseAsync()
// Type inference
z.infer<typeof Schema>, z.input<typeof Schema>, z.output<typeof Schema>
// Error handling
z.flattenError(), z.treeifyError(), z.prettifyError()
// JSON Schema
z.toJSONSchema(schema, options)
// Metadata
.meta(), .describe()
// Object methods
.extend(), .safeExtend(), .pick(), .omit(), .partial(), .required()
.catchall(), .keyof() // .merge() is deprecated — use .extend(other.shape)
When to Load References Load references/migration-guide.md when:
Upgrading from Zod v3 to v4
Questions about breaking changes
Need migration checklist or rollback strategy
Errors related to deprecated APIs (.merge(), error.format(), etc.)
Number validation issues with Infinity or unsafe integers
Load references/error-handling.md when:
Need to format errors for forms or UI
Implementing custom error messages
Questions about z.flattenError(), z.treeifyError(), or z.prettifyError()
Setting up localization for error messages
Need error code reference or pattern examples
Load references/advanced-patterns.md when:
Implementing custom refinements or async validation
Need bidirectional transformations (codecs)
Working with recursive types or self-referential data
Questions about .refine(), .transform(), or .codec()
Need performance optimization patterns
Implementing conditional validation
Load references/type-inference.md when:
Questions about TypeScript type inference
Need to generate JSON Schema for OpenAPI or AI
Implementing metadata system for forms or documentation
Need custom registries for type-safe metadata
Questions about z.infer, z.input, z.output
Using brand types for ID safety
Load references/ecosystem-integrations.md when:
Integrating with tRPC, React Hook Form, Prisma, or NestJS
Setting up ESLint plugins for best practices
Generating Zod schemas from OpenAPI (orval, Hey API, kubb)
Questions about Hono middleware or Drizzle ORM
Need framework-specific integration examples
Load references/troubleshooting.md when:
Encountering TypeScript strict mode errors
Bundle size concerns or lazy loading needs
Performance issues with large unions or async refinements
Questions about circular dependencies
Need best practices or testing patterns
Confusion between .refine() and .transform()
Load references/best-practices.md when:
Writing production Zod code or reviewing Zod usage
Deciding strict vs loose objects, optional vs nullable, any vs unknown
Optimizing hot paths (schema caching, avoiding dynamic schema creation, large arrays)
Unsure where to validate (boundaries, JSON.parse, double validation)
Want Incorrect/Correct examples with "when NOT to use" guidance
Additional Resources
Package version: 4.4.x (all APIs verified against [email protected] )
Zero dependencies
Bundle size: ~5kb core / ~1.9kb zod/mini (gzipped)
TypeScript 5.5+ required
Strict mode required
Last verified: 2026-08-20
Skill version: 2.1.0 (fidelity audit + best-practices rules)
🔍 Fidelity audit: every documented API verified against [email protected] (typecheck + runtime tests)
📜 New references/best-practices.md rulebook (Incorrect/Correct/When-NOT format with impact ratings)
➕ New coverage: zod/mini, .check(), catchall, z.preprocess, z.custom, z.templateLiteral, z.file/z.json/z.stringbool/z.xor, z.exactOptional, .safeExtend, z.fromJSONSchema, expanded format validators
🐛 Fixed: install commands, z.string().uuid() → z.uuid(), v3 .merge()/z.function() chains, .deepPartial() (removed in v4), single-arg z.record, toJSONSchema metadata option
✨ Comprehensive v3 to v4 migration guide with breaking changes
✨ Enhanced error customization with three-level system
✨ Expanded metadata API with registry system
✨ Improved error formatting with practical examples
✨ Built-in localization support for 40+ locales
✨ Detailed codec documentation with real-world patterns
✨ Performance improvements and architectural changes explained