Zod 4 is a major rewrite of the most popular TypeScript validation library, delivering up to 10x faster parsing, a smaller bundle size, and powerful new APIs. If you're building forms, API routes, or any data boundary in your Next.js app, Zod 4 is the tool that keeps your runtime types in sync with TypeScript — automatically.
What's new in Zod 4?
Zod 4 is not just an incremental update — it's a ground-up rewrite focused on performance and developer experience:
- 10x faster parsing: Complete rewrite of the internal validation engine.
- Smaller bundle: Tree-shakeable design, only pay for what you use.
z.interface(): A new way to define object schemas with better TypeScript inference.z.templateLiteral(): Validate template literal strings like`user_${number}`.- JSON Schema output: Native
.toJsonSchema()method on every schema. - Better error messages: Redesigned error formatting with i18n support.
Basic Schemas
If you've used Zod before, the core API is familiar — but everything is faster under the hood:
import { z } from 'zod';
// Primitive schemas
const nameSchema = z.string().min(2).max(100);
const ageSchema = z.number().int().gte(18);
const emailSchema = z.string().email();
const isActiveSchema = z.boolean();
// Object schema
const UserSchema = z.object({
name: nameSchema,
email: emailSchema,
age: ageSchema.optional(),
role: z.enum(['admin', 'editor', 'viewer']),
isActive: isActiveSchema.default(true),
});
// Inferred TypeScript type — always in sync
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number; role: 'admin' | 'editor' | 'viewer'; isActive: boolean }The new z.interface()
Zod 4 introduces z.interface() which provides better TypeScript inference for recursive and complex types:
// Recursive types (e.g., a comment tree)
const CommentSchema = z.interface({
id: z.number(),
body: z.string(),
author: z.string(),
// Self-referencing — z.interface() handles this natively
replies: z.array(z.lazy(() => CommentSchema)).default([]),
});
type Comment = z.infer<typeof CommentSchema>;
// { id: number; body: string; author: string; replies: Comment[] }Template Literal Types
One of the most requested features — validate strings that follow a pattern:
// Validate structured string formats
const userIdSchema = z.templateLiteral([z.literal('user_'), z.number()]);
// Matches: 'user_123', 'user_0', 'user_99999'
// Rejects: 'user_abc', 'admin_123', '123'
const hexColorSchema = z.templateLiteral([
z.literal('#'),
z.string().regex(/^[0-9a-fA-F]{6}$/),
]);
// Matches: '#ff0000', '#1a2b3c'JSON Schema Output
Every Zod 4 schema can be converted to JSON Schema — useful for API documentation, OpenAPI specs, or AI tool definitions:
const ProductSchema = z.object({
name: z.string().min(1).describe('Product name'),
price: z.number().positive().describe('Price in cents'),
category: z.enum(['electronics', 'books', 'clothing']),
});
const jsonSchema = ProductSchema.toJsonSchema();
// {
// type: 'object',
// properties: {
// name: { type: 'string', minLength: 1, description: 'Product name' },
// price: { type: 'number', exclusiveMinimum: 0, description: 'Price in cents' },
// category: { type: 'string', enum: ['electronics', 'books', 'clothing'] }
// },
// required: ['name', 'price', 'category']
// }Zod 4 with React Hook Form
The integration with React Hook Form via @hookform/resolvers works the same way, but now with Zod 4's improved performance:
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const ContactSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Please enter a valid email'),
message: z.string().min(10, 'Message must be at least 10 characters'),
});
type ContactForm = z.infer<typeof ContactSchema>;
export function ContactForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<ContactForm>({
resolver: zodResolver(ContactSchema),
});
const onSubmit = (data: ContactForm) => {
// data is fully typed and validated
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<textarea {...register('message')} placeholder="Message" />
{errors.message && <p>{errors.message.message}</p>}
<button type="submit">Send</button>
</form>
);
}Validation in Server Actions
Use Zod to validate form data in your Next.js Server Actions — one schema, validated on both client and server:
// app/actions.ts
'use server';
import { z } from 'zod';
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.array(z.string()).max(5),
published: z.boolean().default(false),
});
export async function createPost(formData: FormData) {
const raw = {
title: formData.get('title'),
content: formData.get('content'),
tags: formData.getAll('tags'),
published: formData.get('published') === 'true',
};
const result = CreatePostSchema.safeParse(raw);
if (!result.success) {
return {
errors: result.error.flatten().fieldErrors,
};
}
// result.data is fully typed as CreatePost
await db.post.create({ data: result.data });
revalidatePath('/posts');
}Validation in Route Handlers
For API routes, Zod ensures your request bodies are always the shape you expect:
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const CreateUserBody = z.object({
name: z.string().min(2),
email: z.string().email(),
role: z.enum(['admin', 'user']).default('user'),
});
export async function POST(req: NextRequest) {
const body = await req.json();
const parsed = CreateUserBody.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: parsed.error.flatten() },
{ status: 400 }
);
}
// parsed.data is typed as { name: string; email: string; role: 'admin' | 'user' }
const user = await createUser(parsed.data);
return NextResponse.json(user, { status: 201 });
}Migrating from Zod 3
The migration from Zod 3 to Zod 4 is mostly seamless, but here are the key changes:
z.object()still works —z.interface()is additive, not a replacement.- Error formatting has changed — check your custom error handlers.
.toJsonSchema()replaces thezod-to-json-schemapackage.- Some edge cases in
.transform()and.pipe()behave differently — test your pipelines. - Bundle size is smaller, but ensure you import from
'zod'(not deep paths).
Conclusion
Zod 4 cements its position as the essential TypeScript validation library. With 10x faster parsing, native JSON Schema output, and new APIs like z.interface() and z.templateLiteral(), it's the best way to validate data at every boundary of your Next.js application — from forms to API routes to Server Actions.