Skill v1.0.1
currentAutomated scan100/10014 files
version: "1.0.1" name: zod description: TypeScript-first schema validation and type inference. Use for validating API requests/responses, form data, env vars, configs, defining type-safe schemas with runtime validation, transforming data, generating JSON Schema for OpenAPI/AI, or encountering missing validation errors, type inference issues, validation error handling problems. Zero dependencies (2kb gzipped). license: MIT metadata: version: 2.0.0 last_verified: 2025-11-17 package_version: 4.1.12+ keywords:
- zod
- validation
- schema
- typescript
- type-safety
- runtime-validation
- type-inference
- data-validation
- form-validation
- api-validation
- json-schema
- refinement
- transformation
- error-handling
- parse
- safeParse
- z.object
- z.string
- z.number
- z.array
- z.union
- z.discriminatedUnion
- z.refine
- z.transform
- z.infer
- z.coerce
- z.enum
- z.literal
- z.tuple
- z.record
- z.intersection
- z.codec
- z.toJSONSchema
- z.treeifyError
- z.flattenError
- z.prettifyError
- z.registry
- z.globalRegistry
- .register
- .meta
- error-customization
- localization
- i18n
- migration
- v3-to-v4
- breaking-changes
- tRPC
- prisma-zod
- react-hook-form
- trpc
- environment-variables
- env-validation
- config-validation
- dto
- type-guard
- runtime-type-checking
token_savings: 65% errors_prevented: 8 production_tested: true related_skills:
- react-hook-form-zod
- typescript-mcp
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 2kb core bundle (gzipped), Zod provides immutable, composable validation with comprehensive error handling.
Installation
bun add zod# orbun add zod# orbun add zod# oryarn add zod
Requirements:
- TypeScript v5.5+ with
"strict": trueintsconfig.json - Zod 4.x (4.1.12+)
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 transformationsz.iso.date(),z.iso.time(),z.iso.datetime(),z.iso.duration()- ISO format validatorsz.toJSONSchema()- JSON Schema generationz.treeifyError(),z.prettifyError(),z.flattenError()- New error formatting helpers.meta()- Enhanced metadata (Zod 3.x only has.describe())- Unified
errorparameter - Replacesmessage,invalid_type_error,required_error,errorMap
For Zod 3.x compatibility or migration guidance, see https://zod.dev
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
errorparameter (replacesmessage,invalid_type_error,required_error) - Number validation: Stricter - rejects
Infinityand unsafe integers - String formats: Now top-level functions (
z.email()vsz.string().email()) - Object defaults: Applied even in optional fields
- Deprecated APIs: Use
.extend()(not.merge()),z.treeifyError()(noterror.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 schemaconst UserSchema = z.object({username: z.string(),age: z.number().int().positive(),email: z.string().email(),});// Infer TypeScript typetype 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
ZodErroron 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 stringz.string().min(5) // Minimum lengthz.string().max(100) // Maximum lengthz.string().length(10) // Exact lengthz.string().email() // Email validationz.string().url() // URL validationz.string().uuid() // UUID formatz.string().regex(/^\d+$/) // Custom patternz.string().startsWith("pre") // Prefix checkz.string().endsWith("suf") // Suffix checkz.string().trim() // Auto-trim whitespacez.string().toLowerCase() // Auto-lowercasez.string().toUpperCase() // Auto-uppercase// ISO formats (Zod 4+)z.iso.date() // YYYY-MM-DDz.iso.time() // HH:MM:SSz.iso.datetime() // ISO 8601 datetimez.iso.duration() // ISO 8601 duration// Network formatsz.ipv4() // IPv4 addressz.ipv6() // IPv6 addressz.cidrv4() // IPv4 CIDR notationz.cidrv6() // IPv6 CIDR notation// Other formatsz.jwt() // JWT tokenz.nanoid() // Nanoidz.cuid() // CUIDz.cuid2() // CUID2z.ulid() // ULIDz.base64() // Base64 encodedz.hex() // Hexadecimal
Numbers
z.number() // Basic numberz.number().int() // Integer onlyz.number().positive() // > 0z.number().nonnegative() // >= 0z.number().negative() // < 0z.number().nonpositive() // <= 0z.number().min(0) // Minimum valuez.number().max(100) // Maximum valuez.number().gt(0) // Greater thanz.number().gte(0) // Greater than or equalz.number().lt(100) // Less thanz.number().lte(100) // Less than or equalz.number().multipleOf(5) // Must be multiple of 5z.int() // Shorthand for z.number().int()z.int32() // 32-bit integerz.nan() // NaN value
Coercion (Type Conversion)
z.coerce.string() // Convert to stringz.coerce.number() // Convert to numberz.coerce.boolean() // Convert to booleanz.coerce.bigint() // Convert to bigintz.coerce.date() // Convert to Date// Example: Parse query parametersconst 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() // Booleanz.date() // Date objectz.date().min(new Date("2020-01-01"))z.date().max(new Date("2030-12-31"))z.bigint() // BigIntz.symbol() // Symbolz.null() // Nullz.undefined() // Undefinedz.void() // Void (undefined)
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 methodsPersonSchema.shape // Access shapePersonSchema.keyof() // Get union of keysPersonSchema.extend({ role: z.string() }) // Add fieldsPersonSchema.pick({ name: true }) // Pick specific fieldsPersonSchema.omit({ age: true }) // Omit fieldsPersonSchema.partial() // Make all fields optionalPersonSchema.required() // Make all fields requiredPersonSchema.deepPartial() // Recursively optional// Strict vs loose objectsz.strictObject({ ... }) // No extra keys allowed (throws)z.object({ ... }) // Strips extra keys (default)z.looseObject({ ... }) // Allows extra keys
Arrays
z.array(z.string()) // String arrayz.array(z.number()).min(1) // At least 1 elementz.array(z.number()).max(10) // At most 10 elementsz.array(z.number()).length(5) // Exactly 5 elementsz.array(z.number()).nonempty() // At least 1 element// Nested arraysz.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
// Enumconst RoleEnum = z.enum(["admin", "user", "guest"]);type Role = z.infer<typeof RoleEnum>; // "admin" | "user" | "guest"// Literal valuesz.literal("exact_value")z.literal(42)z.literal(true)// Native TypeScript enumenum Fruits {Apple,Banana,}z.nativeEnum(Fruits)// Enum methodsRoleEnum.enum.admin // "admin"RoleEnum.exclude(["guest"]) // Exclude valuesRoleEnum.extract(["admin", "user"]) // Include only
Unions
// Basic unionz.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 }
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 valuesz.record(z.string()) // { [key: string]: string }z.record(z.string(), z.number()) // { [key: string]: number }// Partial record (some keys optional)z.partialRecord(z.enum(["a", "b"]), z.string())// Mapz.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());
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(),});
Recursive Types:
const CategorySchema: z.ZodType<Category> = z.lazy(() =>z.object({ name: z.string(), subcategories: z.array(CategorySchema) }));
Optional/Nullable:
z.string().optional() // string | undefinedz.string().nullable() // string | nullz.string().default("default") // Provides default if undefined
Readonly & Brand:
z.object({ ... }).readonly() // Readonly propertiesz.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 formsconst { fieldErrors } = z.flattenError(error);// For nested dataconst tree = z.treeifyError(error);const nameError = tree.properties?.user?.properties?.name?.errors?.[0];// For debuggingconsole.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 levelschema.parse(data, { error: (issue) => ({ message: "..." }) });// 3. Global levelz.config({ customError: (issue) => ({ message: "..." }) });
Localization (40+ languages):
z.config(z.locales.es()); // Spanishz.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
Basic Type Inference:
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>; // stringtype Output = z.output<typeof TransformSchema>; // number
JSON Schema Conversion:
const jsonSchema = z.toJSONSchema(UserSchema, {target: "openapi-3.0",metadata: true,});
Metadata:
// Add metadataconst EmailSchema = z.string().email().meta({title: "Email Address",description: "User's email address",});// Create custom registryconst 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:
const AddFunction = z.function().args(z.number(), z.number()) // Arguments.returns(z.number()); // Return type// Implement typed functionconst add = AddFunction.implement((a, b) => {return a + b; // Type-checked!});// Async functionsconst FetchFunction = z.function().args(z.string()).returns(z.promise(z.object({ data: z.any() }))).implementAsync(async (url) => {const response = await fetch(url);return response.json();});
Common Patterns
Environment Variables
const EnvSchema = z.object({NODE_ENV: z.enum(["development", "production", "test"]),DATABASE_URL: z.string().url(),PORT: z.coerce.number().int().positive().default(3000),API_KEY: z.string().min(32),});// Validate on startupconst env = EnvSchema.parse(process.env);// Now use typed envconsole.log(env.PORT); // number
API Request Validation
const CreateUserRequest = z.object({username: z.string().min(3).max(20),email: z.string().email(),password: z.string().min(8),age: z.number().int().positive().optional(),});// Express exampleapp.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.string().email("Invalid email"),age: z.coerce.number().int().min(18, "Must be 18+"),agreeToTerms: z.literal(true, {errorMap: () => ({ message: "Must accept terms" }),}),});type FormData = z.infer<typeof FormSchema>;
Partial Updates
const UserSchema = z.object({id: z.string(),name: z.string(),email: z.string().email(),});// For PATCH requests: make everything optional except idconst UpdateUserSchema = UserSchema.partial().required({ id: true });type UpdateUser = z.infer<typeof UpdateUserSchema>;// { id: string; name?: string; email?: string }
Composable Schemas
// Base schemasconst TimestampSchema = z.object({createdAt: z.date(),updatedAt: z.date(),});const AuthorSchema = z.object({authorId: z.string(),authorName: z.string(),});// Compose into larger schemasconst PostSchema = z.object({id: z.string(),title: z.string(),content: z.string(),}).merge(TimestampSchema).merge(AuthorSchema);
Ecosystem Integration
Load `references/ecosystem-integrations.md` for complete framework and tooling integration guide.
Quick Reference
ESLint Plugins:
eslint-plugin-zod-x- Enforces best practiceseslint-plugin-import-zod- Enforces import style
Framework Integrations:
- tRPC - End-to-end typesafe APIs
- React Hook Form - Form validation (see
react-hook-form-zodskill) - Prisma - Generate Zod from database models
- NestJS - DTOs and validation pipes
Code Generation:
- 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
Common Issues:
- 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
Performance Tips:
- Use
.discriminatedUnion()(5-10x faster than.union()) - Cache schema instances
- Use
.safeParse()(avoids try-catch overhead) - Lazy load large schemas
Best Practices:
- 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
// Primitivesz.string(), z.number(), z.boolean(), z.date(), z.bigint()// Collectionsz.array(), z.tuple(), z.object(), z.record(), z.map(), z.set()// Special typesz.enum(), z.union(), z.discriminatedUnion(), z.intersection()z.literal(), z.any(), z.unknown(), z.never()// Modifiers.optional(), .nullable(), .nullish(), .default(), .catch().readonly(), .brand()// Validation.min(), .max(), .length(), .regex(), .email(), .url(), .uuid().refine(), .superRefine()// Transformation.transform(), .pipe(), .codec()// Parsing.parse(), .safeParse(), .parseAsync(), .safeParseAsync()// Type inferencez.infer<typeof Schema>, z.input<typeof Schema>, z.output<typeof Schema>// Error handlingz.flattenError(), z.treeifyError(), z.prettifyError()// JSON Schemaz.toJSONSchema(schema, options)// Metadata.meta(), .describe()// Object methods.extend(), .pick(), .omit(), .partial(), .required(), .merge()
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
Infinityor 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(), orz.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()
Additional Resources
- Official Docs: https://zod.dev
- GitHub: https://github.com/colinhacks/zod
- TypeScript Playground: https://zod-playground.vercel.app
- ESLint Plugin (Best Practices): https://github.com/JoshuaKGoldberg/eslint-plugin-zod-x
- tRPC Integration: https://trpc.io
- Ecosystem: https://zod.dev/ecosystem
Production Notes:
- Package version: 4.1.12+ (Zod 4.x stable)
- Zero dependencies
- Bundle size: 2kb (gzipped)
- TypeScript 5.5+ required
- Strict mode required
- Last verified: 2025-11-17
- Skill version: 2.0.0 (Updated with v4.1 enhancements)
What's New in This Version:
- ✨ 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