Build Form Validation Schemas from JSON with Zod
Learn how to convert JSON form data structures into Zod schemas with validation rules for email, password, length, and custom refinements.
Advanced
Detailed Explanation
Form Validation with Zod
Zod excels at form validation because it provides both the validation logic and the TypeScript types from a single schema definition.
Example Form JSON
{
"name": "John Doe",
"email": "john@example.com",
"password": "secret123",
"confirmPassword": "secret123",
"age": 25,
"website": "https://johndoe.dev",
"acceptTerms": true
}
Zod Validation Schema
import { z } from "zod";
const registrationSchema = z.object({
name: z.string()
.min(1, "Name is required")
.max(100, "Name must be 100 characters or less"),
email: z.string()
.email("Please enter a valid email address"),
password: z.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Must contain an uppercase letter")
.regex(/[0-9]/, "Must contain a number"),
confirmPassword: z.string(),
age: z.number()
.int()
.min(13, "Must be at least 13 years old")
.max(120),
website: z.string()
.url("Must be a valid URL")
.optional(),
acceptTerms: z.literal(true, {
errorMap: () => ({ message: "You must accept the terms" }),
}),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
type RegistrationForm = z.infer<typeof registrationSchema>;
Cross-Field Validation with .refine()
The .refine() method enables validation that depends on multiple fields:
const dateRangeSchema = z.object({
startDate: z.string().date(),
endDate: z.string().date(),
}).refine((data) => data.endDate >= data.startDate, {
message: "End date must be after start date",
path: ["endDate"],
});
Integration with React Hook Form
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
function RegistrationForm() {
const form = useForm<RegistrationForm>({
resolver: zodResolver(registrationSchema),
});
// Type-safe form handling with runtime validation
}
Superrefine for Complex Logic
const schema = z.object({
type: z.enum(["personal", "business"]),
companyName: z.string().optional(),
}).superRefine((data, ctx) => {
if (data.type === "business" && !data.companyName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Company name required for business accounts",
path: ["companyName"],
});
}
});
This declarative approach to form validation eliminates scattered if-else validation logic and keeps all rules in one place.
Use Case
You are building a multi-step registration form with React Hook Form and need a single Zod schema that validates all fields, handles cross-field checks like password confirmation, and provides type-safe form state.