How to Use Zod with React
Practical guide to Zod in React apps — form validation, React Hook Form integration, API response validation, and TypeScript type inference.
1. Install Zod
npm install zod
# or
yarn add zod2. Define a Schema and Infer the TypeScript Type
Create a schema once — Zod infers the TypeScript type automatically:
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
age: z.number().int().min(18, "Must be 18 or older").optional(),
});
// TypeScript type inferred from schema
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }3. Validate Data with safeParse
Use safeParse to validate without throwing — returns a success/error result:
const data = { name: "Alice", email: "alice@example.com" };
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data is fully typed as User
console.log(result.data.name);
} else {
// result.error.flatten() gives field-level errors
const errors = result.error.flatten();
console.log(errors.fieldErrors);
// { email: ["Invalid email address"] }
}4. Validate API Responses in React
Parse API responses to ensure they match your expected shape:
import { useState, useEffect } from 'react';
import { z } from 'zod';
const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
userId: z.number(),
});
type Post = z.infer<typeof PostSchema>;
function usePost(id: number) {
const [post, setPost] = useState<Post | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(r => r.json())
.then(data => {
const result = PostSchema.safeParse(data);
if (result.success) {
setPost(result.data);
} else {
setError("API response did not match expected shape");
console.error(result.error.flatten());
}
});
}, [id]);
return { post, error };
}5. React Hook Form + Zod (Recommended)
Install the resolver:
npm install react-hook-form @hookform/resolversUse the Zod resolver in your form:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const LoginSchema = z.object({
email: z.string().email("Enter a valid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
type LoginData = z.infer<typeof LoginSchema>;
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginData>({
resolver: zodResolver(LoginSchema),
});
const onSubmit = (data: LoginData) => {
// data is typed as LoginData — no manual type assertion needed
console.log(data.email, data.password);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} type="email" placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<input {...register("password")} type="password" placeholder="Password" />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Login</button>
</form>
);
}6. Environment Variable Validation
Validate environment variables at startup so missing vars fail fast:
import { z } from 'zod';
const EnvSchema = z.object({
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_KEY: z.string().startsWith("pk_"),
DATABASE_URL: z.string().min(1),
});
// Throws at startup if env vars are missing or invalid
const env = EnvSchema.parse(process.env);
export default env;7. Reusable Field Schemas
Extract common fields to avoid repetition:
import { z } from 'zod';
// Reusable building blocks
const emailField = z.string().email("Invalid email");
const passwordField = z.string()
.min(8, "At least 8 characters")
.regex(/[A-Z]/, "Must contain an uppercase letter")
.regex(/[0-9]/, "Must contain a number");
const LoginSchema = z.object({
email: emailField,
password: passwordField,
});
const RegisterSchema = z.object({
email: emailField,
password: passwordField,
confirmPassword: passwordField,
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});Frequently Asked Questions
Do I need TypeScript to use Zod?
No, but Zod is most useful with TypeScript. The z.infer<typeof Schema> feature automatically derives TypeScript types from schemas, eliminating the need to write types and validation separately.
What is the difference between parse and safeParse in Zod?
parse() throws a ZodError if validation fails. safeParse() returns { success: true, data } or { success: false, error } — it never throws. Use safeParse() in most React code to handle errors gracefully without try/catch.
Is Zod or Yup better for React Hook Form?
Both integrate with React Hook Form via @hookform/resolvers. Zod is generally preferred for TypeScript projects because types are inferred from the schema automatically. Yup is the better choice if you use Formik or have an existing Yup codebase.
How do I show Zod validation errors in a React form?
Use React Hook Form with zodResolver — errors appear in formState.errors keyed by field name. Without React Hook Form, call safeParse() on submit and use result.error.flatten().fieldErrors to get field-level messages.
Can I use Zod to validate environment variables in Next.js?
Yes. Define a Zod schema for process.env and call schema.parse(process.env) at the top of a config file. This throws at startup if required variables are missing or invalid, catching config errors before they reach production.
From the blog
View all →
May 13, 2026
Convert XML to JSON Online: Complete Guide for Developers (2026)

May 11, 2026
Convert CSV to JSON Online Free (Best Developer Guide 2026)

May 6, 2026
Convert YAML to JSON Online Free (2026 Developer Guide)

Apr 30, 2026
Convert JSON to Zod Schema Online (2026) – Free Tool & Complete Guide

Apr 30, 2026
Top 50 JavaScript to TypeScript Converters (2026) – Free, AI & Online Tools
Generate a Zod schema from your JSON data automatically?
Use the JSON to Zod Converter →