Integrate Zod v4 with React Hook Form, Next.js, Express, tRPC, and other frameworks for type-safe validation
Integrates Zod v4 with React Hook Form, Next.js Server Actions, Express, and tRPC for type-safe validation. Use when building forms or APIs to automatically validate user input at boundaries with proper error handling and type inference.
/plugin marketplace add djankies/claude-configs/plugin install zod-4@claude-configsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/framework-examples.mdComprehensive guide to integrating Zod v4 validation with popular frameworks including React Hook Form, Next.js Server Actions, Express APIs, tRPC, and more.
Zod integrates seamlessly with:
Frontend Frameworks:
zodResolverBackend Frameworks:
.input()Database/ORM:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const formSchema = z.object({
email: z.email().trim().toLowerCase(),
password: z.string().min(8)
});
type FormData = z.infer<typeof formSchema>;
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(formSchema)
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
'use server';
const schema = z.object({
email: z.email().trim().toLowerCase()
});
export async function createUser(formData: FormData) {
const result = schema.safeParse({
email: formData.get('email')
});
if (!result.success) {
return { errors: result.error.flatten().fieldErrors };
}
return { data: await db.create(result.data) };
}
import express from 'express';
function validate<T extends z.ZodTypeAny>(schema: T) {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error.flatten() });
}
req.body = result.data;
next();
};
}
const userSchema = z.object({ email: z.email() });
app.post('/users', validate(userSchema), async (req, res) => {
const user = await createUser(req.body);
res.json(user);
});
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const createUserInput = z.object({
email: z.email().trim().toLowerCase(),
name: z.string().trim().min(1)
});
export const appRouter = t.router({
createUser: t.procedure
.input(createUserInput)
.mutation(async ({ input }) => {
return await db.users.create({ data: input });
})
});
const schema = z.object({...}); // ✅ Reusable
function handler() {
const schema = z.object({...}); // ❌ Recreated
}
type FormData = z.infer<typeof formSchema>; // ✅
interface FormData { ... } // ❌ Duplicates schema
z.email().trim().toLowerCase() // ✅ Normalize
z.email() // ❌ No normalization
const result = schema.safeParse(userInput); // ✅
try { schema.parse(userInput) } // ❌
z.email({ error: "Invalid email" }) // ✅
z.email() // ❌ Generic error
export async function createUser(data: unknown) {
const validated = schema.parse(data); // ✅
}
export async function createUser(data: User) {
await db.create(data); // ❌ No validation
}
zodResolver for automatic integrationvalueAsNumber for number inputssafeParsez.stringbool() for checkbox values.input() with Zod schemasCross-Plugin References:
React 19 Integration:
Next.js 16 Integration:
Prisma 6 Integration: