Our review
Zod v4 is a TypeScript-first schema validation library that provides static type inference and runtime validation with fast parsing and small bundle size.
Strengths
- Extremely fast parsing (14x faster than v3)
- Small bundle size (66% smaller)
- Rich type inference and comprehensive built-in validators for formats like email, UUID, URLs, IP addresses, etc.
Limitations
- Requires TypeScript environment for full benefits
- Learning curve for advanced patterns like discriminated unions and template literals
- May be overkill for simple validation needs
Use Zod v4 when you need robust runtime validation and type safety for complex data structures in TypeScript applications.
Avoid Zod v4 if you need only simple null checks or if your project is not using TypeScript.
Security analysis
SafeThe skill file contains only documentation and examples for a TypeScript validation library, with no executable commands, no external calls, and no destructive or data-exfiltrating instructions.
No concerns found
Examples
Create a Zod schema for a user with id (positive integer), email, username (3-20 chars, alphanumeric + underscore), age (13-120), and role (enum with default 'user'). Then parse an example object.Define a Zod discriminated union schema for API responses based on a 'status' field: 'success' with data, 'error' with error message and code, 'loading' with optional message. Then show how to parse a sample response.Create a Zod template literal schema that validates a version string like '1.2.3' (three numbers separated by dots). Then parse a valid and an invalid example.name: "Zod v4" description: "TypeScript-first schema validation library with static type inference and runtime validation" when_to_use: "When you need robust data validation, type safety, schema definitions, or input validation in TypeScript/JavaScript applications"
Zod v4 - Schema Validation Skill
Zod v4 is a TypeScript-first schema declaration and validation library that provides static type inference, runtime validation, and exceptional performance. Version 4 delivers 14x faster parsing and 66% smaller bundles while maintaining full type safety.
Quick Start
npm install zod@^4.0.0
import * as z from "zod";
// Basic schema creation and validation
const UserSchema = z.object({
id: z.number().int().positive(),
email: z.email(),
username: z.string().min(3).max(20),
age: z.number().int().min(18).optional(),
});
type User = z.infer<typeof UserSchema>;
// Parse and validate
const user = UserSchema.parse({
id: 1,
email: "user@example.com",
username: "johndoe",
age: 25,
});
// Safe parsing with error handling
const result = UserSchema.safeParse(input);
if (!result.success) {
console.log(result.error);
}
Common Patterns
Object Schemas with Validation
// Comprehensive user validation
const UserProfile = z.object({
id: z.number().int().positive(),
email: z.email(),
username: z
.string()
.min(3, "Username must be at least 3 characters")
.max(20, "Username cannot exceed 20 characters")
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric characters and underscores"),
age: z.number().int().min(13).max(120),
role: z.enum(["user", "admin", "moderator"]).default("user"),
bio: z.string().max(500).optional(),
website: z.url().optional(),
createdAt: z.date().default(() => new Date()),
});
// Extending schemas
const AdminProfile = UserProfile.extend({
permissions: z.array(z.string()),
accessLevel: z.number().min(1).max(10),
});
String Format Validation
// Built-in format validators
const Validations = {
email: z.email(),
uuid: z.uuidv4(),
url: z.url(),
ipv4: z.ipv4(),
ipv6: z.ipv6(),
base64: z.base64(),
jwt: z.jwt(),
// ISO formats
isoDate: z.iso.date(),
isoDateTime: z.iso.datetime(),
isoTime: z.iso.time(),
// Custom email with specific pattern
strictEmail: z.email({ pattern: z.regexes.rfc5322Email }),
};
// Template literal validation
const VersionString = z.templateLiteral([
z.number(),
".",
z.number(),
".",
z.number(),
]);
const CSSValue = z.templateLiteral([
z.number(),
z.enum(["px", "em", "rem", "%", "vh", "vw"]),
]);
Array and Collection Validation
// Array with constraints
const NumberArray = z.array(z.number()).min(1).max(100);
// Tuple validation
const Coordinates = z.tuple([z.number(), z.number()]);
const MixedTuple = z.tuple([z.string(), z.number()], z.boolean());
// Set validation
const UniqueStrings = z.set(z.string()).min(3);
// Map validation
const UserPermissions = z.map(
z.string(), // user ID
z.array(z.string()), // permissions
);
// Record validation
const StringToNumber = z.record(z.string(), z.number());
const StatusRecord = z.record(
z.enum(["pending", "active", "complete"]),
z.boolean(),
);
Union and Discriminated Unions
// Simple union
const StringOrNumber = z.union([z.string(), z.number()]);
// Discriminated union for API responses
const ApiResponse = z.discriminatedUnion("status", [
z.object({
status: z.literal("success"),
data: z.unknown(),
}),
z.object({
status: z.literal("error"),
error: z.string(),
code: z.number(),
}),
z.object({
status: z.literal("loading"),
message: z.string().optional(),
}),
]);
// Nested discriminated unions
const BaseError = z.object({
status: z.literal("error"),
message: z.string(),
});
const DetailedError = z.discriminatedUnion("code", [
BaseError.extend({ code: z.literal(400), field: z.string() }),
BaseError.extend({ code: z.literal(401), realm: z.string() }),
BaseError.extend({ code: z.literal(500), stack: z.string() }),
]);
Custom Refinements and Validation
// Custom validation with refinements
const PasswordSchema = z
.string()
.min(8, "Password must be at least 8 characters")
.refine((val) => /[A-Z]/.test(val), "Must contain uppercase letter")
.refine((val) => /[a-z]/.test(val), "Must contain lowercase letter")
.refine((val) => /[0-9]/.test(val), "Must contain number")
.refine((val) => /[^A-Za-z0-9]/.test(val), "Must contain special character");
// Complex validation with superRefine
const UserRegistration = z
.object({
email: z.email(),
password: z.string(),
confirmPassword: z.string(),
age: z.number(),
termsAccepted: z.boolean(),
})
.superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: "custom",
path: ["confirmPassword"],
message: "Passwords must match",
});
}
if (data.age < 18 && !data.termsAccepted) {
ctx.addIssue({
code: "custom",
path: ["termsAccepted"],
message: "Parental consent required for users under 18",
});
}
});
Transformations and Data Processing
// Transform data during validation
const StringToNumber = z
.string()
.transform((val) => parseInt(val, 10))
.refine((val) => !isNaN(val), "Must be a valid number");
const TimestampToDate = z
.number()
.transform((timestamp) => new Date(timestamp));
const NormalizeEmail = z.string().transform((val) => val.toLowerCase().trim());
// Overwrite for type-preserving transforms
const RoundNumber = z.number().overwrite((val) => Math.round(val));
// Pipeline transformations
const ProcessUrl = z
.string()
.transform((val) => val.trim())
.transform((val) => val.toLowerCase())
.transform((val) => {
try {
return new URL(val);
} catch {
throw new Error("Invalid URL format");
}
});
Recursive Schemas
// Recursive category structure
const Category = z.object({
id: z.number(),
name: z.string(),
get subcategories() {
return z.array(Category);
},
});
// Mutually recursive types
const User = z.object({
id: z.number(),
name: z.string(),
get posts() {
return z.array(Post);
},
});
const Post = z.object({
id: z.number(),
title: z.string(),
content: z.string(),
get author() {
return User;
},
get comments() {
return z.array(Comment);
},
});
const Comment = z.object({
id: z.number(),
text: z.string(),
get author() {
return User.pick({ id: true, name: true });
},
});
File Validation
// File upload validation
const ImageUpload = z.object({
avatar: z
.file()
.max(5_000_000, "File must be less than 5MB")
.mime(["image/jpeg", "image/png", "image/webp"], "Must be an image"),
banner: z
.file()
.max(10_000_000, "File must be less than 10MB")
.mime(["image/jpeg", "image/png"], "Must be JPEG or PNG")
.optional(),
});
// Document validation
const DocumentUpload = z
.file()
.min(1000, "File must be at least 1KB")
.max(50_000_000, "File must be less than 50MB")
.mime([
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
]);
Function Validation
// Define validated functions
const CalculateTax = z.function({
input: [z.number().min(0), z.number().min(0).max(1)],
output: z.number(),
});
const taxCalculator = CalculateTax.implement((amount, rate) => {
return amount * rate;
});
// Async function validation
const FetchUser = z.function({
input: [z.number().int().positive()],
output: z.object({
id: z.number(),
name: z.string(),
email: z.email(),
}),
});
const getUser = FetchUser.implementAsync(async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
});
Error Handling
// Custom error messages
const CustomValidation = z.object({
username: z
.string({
error: (issue) => {
if (issue.input === undefined) return "Username is required";
if (typeof issue.input !== "string") return "Username must be a string";
return "Invalid username";
},
})
.min(3, "Username must be at least 3 characters"),
});
// Error handling patterns
const validateInput = (input: unknown) => {
const result = UserSchema.safeParse(input);
if (!result.success) {
const error = result.error;
// Pretty print errors
console.log(z.prettifyError(error));
// Extract field errors
const fieldErrors = error.issues.reduce(
(acc, issue) => {
const field = issue.path.join(".");
acc[field] = issue.message;
return acc;
},
{} as Record<string, string>,
);
return { success: false, errors: fieldErrors };
}
return { success: true, data: result.data };
};
Default Values and Coercion
// Schema with defaults
const ConfigSchema = z.object({
theme: z.enum(["light", "dark"]).default("light"),
notifications: z.boolean().default(true),
fontSize: z.number().min(10).max(30).default(14),
timeout: z.number().default(5000),
});
// Type coercion
const CoercedConfig = z.object({
port: z.coerce.number().default(3000),
https: z.coerce.boolean().default(false),
maxConnections: z.coerce.number().int().positive().default(100),
});
// Environment variable parsing
const EnvSchema = z.object({
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
PORT: z.coerce.number().default(3000),
DEBUG: z.stringbool().default("false"),
});
Zod Mini (Tree-Shakable)
import * as z from "zod/mini";
// Functional API for smaller bundles
const OptionalString = z.optional(z.string());
const StringArray = z.array(z.string());
const StringOrNumber = z.union([z.string(), z.number()]);
// Check functions for validations
const ValidatedEmail = z
.string()
.check(z.regex(/@/), z.minLength(5), z.maxLength(100));
const PositiveInt = z.number().check(z.int(), z.positive(), z.lt(1000));
const NonEmptyArray = z.array(z.any()).check(z.minSize(1));
Practical Examples
Form Validation
// Contact form validation
const ContactForm = z.object({
name: z.string().min(1, "Name is required").max(100),
email: z.email("Please provide a valid email"),
subject: z.string().min(5, "Subject must be at least 5 characters"),
message: z.string().min(10, "Message must be at least 10 characters"),
newsletter: z.boolean().default(false),
});
// React form integration
const handleSubmit = (formData: FormData) => {
const data = {
name: formData.get("name"),
email: formData.get("email"),
subject: formData.get("subject"),
message: formData.get("message"),
newsletter: formData.get("newsletter") === "on",
};
const result = ContactForm.safeParse(data);
if (!result.success) {
const errors = result.error.flatten();
return { errors: errors.fieldErrors };
}
// Process valid data
return { success: true, data: result.data };
};
API Response Validation
// API response schemas
const UserResponse = z.object({
data: z.object({
id: z.number(),
name: z.string(),
email: z.email(),
createdAt: z.string().transform((val) => new Date(val)),
}),
meta: z.object({
total: z.number(),
page: z.number(),
totalPages: z.number(),
}),
});
// Typed API client
const apiClient = {
async getUser(id: number): Promise<z.infer<typeof UserResponse>["data"]> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
const result = UserResponse.safeParse(data);
if (!result.success) {
throw new Error(`Invalid API response: ${result.error.message}`);
}
return result.data.data;
},
};
Configuration Validation
// Application configuration
const AppConfig = z.object({
server: z.object({
port: z.coerce.number().min(1).max(65535).default(3000),
host: z.string().default("localhost"),
cors: z.boolean().default(true),
}),
database: z.object({
url: z.string(),
ssl: z.boolean().default(false),
maxConnections: z.coerce.number().int().positive().default(10),
}),
auth: z.object({
jwtSecret: z.string().min(32),
tokenExpiry: z.string().default("24h"),
refreshExpiry: z.string().default("7d"),
}),
});
// Load and validate config
const loadConfig = (configPath: string) => {
const rawConfig = require(configPath);
const config = AppConfig.parse(rawConfig);
return config;
};
Requirements
- TypeScript: 4.5+ (recommended for best inference)
- Runtime: Node.js, browsers, Deno, Bun
- Bundle size: 5.36kb gzipped (full), 1.88kb gzipped (mini)
Installation
# Full Zod v4
npm install zod@^4.0.0
# For minimal bundle size
npm install zod@^4.0.0
# Then import from "zod/mini"
Key Features
- Static Type Inference: Automatic TypeScript type generation from schemas
- Runtime Validation: Comprehensive input validation with detailed errors
- Performance: 14x faster parsing than v3, optimized for production
- Tree Shakable: Zod Mini provides 85% bundle size reduction
- Template Literals: Validate string patterns matching TypeScript template literals
- File Validation: Built-in File object validation with size and MIME type constraints
- Recursive Types: Full support for recursive and self-referential schemas
- JSON Schema: First-party JSON Schema generation
- Function Validation: Type-safe function definitions with validated inputs/outputs
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.