What is Zod
Zod is a TypeScript-first schema declaration and validation library. Just by defining a schema, you can perform validation and type inference simultaneously.
Zod 4 New Features
Performance Improvements
Validation Speed:
- Zod 3: 100,000 ops/sec
- Zod 4: 300,000 ops/sec (3x faster)
Parse Speed:
- Up to 5x faster with complex schemas
New Metadata API
import { z } from 'zod';
const userSchema = z.object({
name: z.string().describe('User name'),
email: z.string().email().describe('Email address'),
age: z.number().min(0).max(150).describe('Age')
}).describe('User information');
// Get metadata
const metadata = userSchema.description;
const fields = userSchema.shape;
Basic Usage
Schema Definition
import { z } from 'zod';
// Primitive types
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
// Object
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
age: z.number().int().positive().optional(),
role: z.enum(['user', 'admin']),
createdAt: z.date().default(() => new Date())
});
// Type inference
type User = z.infer<typeof userSchema>;
// {
// id: number;
// name: string;
// email: string;
// age?: number;
// role: 'user' | 'admin';
// createdAt: Date;
// }
Validation
// parse: Throws exception on failure
const user = userSchema.parse({
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'user'
});
// safeParse: Returns result as object
const result = userSchema.safeParse(input);
if (result.success) {
console.log(result.data);
} else {
console.error(result.error.issues);
}
Transform
const schema = z.string()
.transform(val => val.toLowerCase())
.transform(val => val.trim());
const cleanSchema = z.object({
price: z.string().transform(val => parseFloat(val)),
quantity: z.string().transform(val => parseInt(val, 10))
});
Advanced Features
Refinement
const passwordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.refine(
(val) => /[A-Z]/.test(val),
{ message: 'Please include an uppercase letter' }
)
.refine(
(val) => /[0-9]/.test(val),
{ message: 'Please include a number' }
);
// superRefine (return multiple errors)
const formSchema = z.object({
password: z.string(),
confirmPassword: z.string()
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Passwords do not match',
path: ['confirmPassword']
});
}
});
Union and Discriminator
// Regular union
const responseSchema = z.union([
z.object({ status: z.literal('success'), data: z.any() }),
z.object({ status: z.literal('error'), message: z.string() })
]);
// Discriminated union (faster)
const eventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
z.object({ type: z.literal('scroll'), offset: z.number() }),
z.object({ type: z.literal('keypress'), key: z.string() })
]);
Recursive Schema
interface Category {
name: string;
subcategories: Category[];
}
const categorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(categorySchema)
})
);
Partial and Required
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string()
});
// Make all optional
const partialUser = userSchema.partial();
// Make specific fields optional only
const updateUser = userSchema.partial({
name: true,
email: true
});
// Make optional fields required
const requiredUser = userSchema.required();
API Validation
Express
import express from 'express';
import { z } from 'zod';
const createUserSchema = z.object({
body: z.object({
name: z.string(),
email: z.string().email()
}),
query: z.object({
notify: z.string().optional()
})
});
function validate<T extends z.ZodType>(schema: T) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
next();
};
}
app.post('/users', validate(createUserSchema), (req, res) => {
// Validated
});
tRPC
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
export const appRouter = router({
createUser: publicProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email()
}))
.mutation(async ({ input }) => {
return db.user.create({ data: input });
})
});
Integration with Form Libraries
React Hook Form
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Please enter a valid email address')
});
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema)
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
{/* ... */}
</form>
);
}
Summary
Zod 4 makes TypeScript schema validation even faster and easier to use. With its integration with type inference, rich validation features, and framework compatibility, it’s an essential tool for type-safe application development.
← Back to list