React

Validation with Zod

Zod validates an object at runtime and derives its TypeScript type: one schema, errors per field, and a type that cannot drift from the rule.

Key points

  • The Zod schema is the single source of the rule and the type.
  • safeParse returns a result instead of throwing.
  • fieldErrors is already a map keyed by field, ready for the interface.
  • The same schema must run again on the server.

Zod validates an object at runtime and derives the TypeScript type: you declare the schema once and get errors per field plus a type that cannot drift from the rule.

The schema is the contract

A Zod schema describes an object's shape and rules, and z.infer extracts the matching type: rule and type come from the same line, so they cannot drift apart after an edit.

import { z } from "zod";

export const signupSchema = z.object({
  email: z.email(),
  password: z.string().min(12),
  age: z.coerce.number().int().min(18),
});

export type SignupValues = z.infer;

Reading errors per field

const parsed = signupSchema.safeParse({ email: "nope", password: "short", age: "17" });

if (!parsed.success) {
  const errors = parsed.error.flatten().fieldErrors;
  // { email: ["Invalid email"], password: [...], age: [...] }
}

safeParse does not throw: it returns a result with success and, on failure, a map keyed by field. That map is ready for the interface, because each key matches an input.

The same schema on the server

const body: unknown = await request.json();
const parsed = signupSchema.safeParse(body);

if (!parsed.success) {
  return Response.json({ errors: parsed.error.flatten() }, { status: 400 });
}

Validation in the browser is a convenience, not a control: a hand-built request skips it. The same schema must run again on the server, where the data really enters the system.

The command

npm install zod
npx tsc --noEmit

Zod ships under the MIT licence; the second command checks that the type derived from the schema compiles without errors in the project.

Live records in the catalogue

Three records show the same idea of a field with its error attached: credit-card-form, login-form and the form blocks category, where each block already carries its error states.

The limitation

A schema checks shape, not truth: a syntactically valid email address may not exist, and an age of 18 proves nothing. Uniqueness, permissions and the existence of the record remain the database's job.

Where to continue

Who owns a field's value is in controlled and uncontrolled forms; server-side validation is in Server Actions and validation; wiring it to a form is in react-hook-form with Zod; the library comparison is in React form libraries.

Frequently asked questions

Does Zod validate at runtime or only in TypeScript?

At runtime. The type is a consequence: the schema checks real values and derives the TypeScript type from them.

safeParse or parse?

safeParse when the error is expected and must be shown to the user; parse when an invalid value is a bug and should stop execution.

Can I reuse the same schema on the server?

Yes, and that is the point: the same schema object runs in Node and in the browser, so the rule is not duplicated.

Does Zod replace HTML validation?

No. Browser attributes give instant feedback without JavaScript; Zod adds rules that HTML cannot express.

Why is zod so large on npm?

Because the published package contains every build, locale and type. Only the imported part reaches the browser, after tree shaking.