React Hook Form with Zod
Validate type-safe React forms with Zod and zodResolver.
Use zodResolver to validate form values against a Zod schema. Zod issues become React Hook Form errors available through formState.errors.
Installation
npm install react-hook-form @hookform/resolvers zod
Complete TypeScript example
import { zodResolver } from "@hookform/resolvers/zod"import { useForm, type SubmitHandler } from "react-hook-form"import { z } from "zod"const schema = z.object({email: z.string().email("Enter a valid email address"),age: z.number().min(18, "You must be at least 18"),})type FormValues = z.infer<typeof schema>export function ProfileForm() {const {register,handleSubmit,formState: { errors },} = useForm<FormValues>({ resolver: zodResolver(schema) })const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("email")} />{errors.email && <p role="alert">{errors.email.message}</p>}<input type="number" {...register("age", { valueAsNumber: true })} />{errors.age && <p role="alert">{errors.age.message}</p>}<button type="submit">Save</button></form>)}
valueAsNumber converts the browser's input string before Zod receives it. Without it, a z.number() schema receives a string unless the schema performs its own coercion.
Input and output types
Most schemas infer the submitted output automatically. When a schema transforms its input, declare both sides explicitly:
useForm<z.input<typeof schema>, unknown, z.output<typeof schema>>({resolver: zodResolver(schema),})
Use z.input for values entered by the form and z.output for the parsed data received by handleSubmit.
Common issues
- Keep validation rules in the schema instead of duplicating them in
register. - Use
z.coerce.number()orvalueAsNumberfor numeric HTML inputs. - Give controlled components a defined
defaultValueordefaultValuesentry. - For all messages from a field, use
criteriaMode: "all"and render the collected error types.
See all schema resolvers, useForm, and the TypeScript guide.