Zod
Overview
Zod is a TypeScript-first schema validation library with static type inference. Define a schema once; you get both runtime validation and a TypeScript type (via z.infer). Zero external dependencies; works in Node and browsers. Ideal for forms (e.g. with react-hook-form), API parsing, env vars, and any untrusted input.
Requirements: TypeScript 5.5+ recommended; enable strict in tsconfig.json.
Install: Add zod via your package manager. See zod.dev.
Quick start
import { z } from "zod";
const User = z.object({
name: z.string(),
age: z.number().optional(),
});
type User = z.infer<typeof User>;
// { name: string; age?: number }
const data = User.parse(input); // throws ZodError if invalid
const result = User.safeParse(input); // { success: true, data } | { success: false, error }
Primitives and common types
| Schema |
Type |
Notes |
z.string() |
string |
|
z.number() |
number |
|
z.boolean() |
boolean |
|
z.bigint() |
bigint |
|
z.date() |
Date |
|
z.undefined() |
undefined |
|
z.null() |
null |
|
z.void() |
void |
|
z.any() |
any |
|
z.unknown() |
unknown |
|
z.never() |
never |
|
z.literal("x") |
literal |
|
z.enum(["a", "b"]) |
union of literals |
|
z.string().email() |
string |
email format |
z.string().url() |
string |
URL |
z.string().uuid() |
string |
UUID |
z.string().datetime() |
string |
ISO datetime |
z.number().int() |
number |
integer |
z.number().min(n).max(m) |
number |
range |
z.boolean() / z.string().transform(...) |
stringbool |
"true"/"false" coercion |
Optional: .optional() → T | undefined. Nullable: .nullable() → T | null. Nullish: .nullish() → T | null | undefined. Default: .default(value).
Objects
const Schema = z.object({
name: z.string(),
age: z.number().optional(),
tags: z.array(z.string()).default([]),
});
type Schema = z.infer<typeof Schema>;
- .shape:
Schema.shape.name (access field schema).
- .keyof():
Schema.keyof() → enum of keys.
- .extend({ ... }): Add or override keys.
- .pick({ name }) / .omit({ age }): Subset of keys.
- .partial() / .required({ name }): Optionalize or require.
- .strict(): No extra keys (default in z.object). z.strictObject / z.looseObject for behavior variants.
- .catchall(z.string()): Allow extra keys with a schema.
- Nested:
z.object({ user: z.object({ name: z.string() }) }).
- Recursive:
z.lazy(() => Category) for self-referential structures.
Arrays and tuples
- Arrays:
z.array(z.string()), z.string().array().
- Tuples:
z.tuple([z.string(), z.number()]) — fixed length and types.
- Non-empty:
.min(1) or dedicated helpers if available.
Unions and intersections
- Union:
z.union([z.string(), z.number()]) or z.string().or(z.number()).
- Discriminated union: Use a common key (e.g.
type: "a") and z.discriminatedUnion("type", [z.object({ type: z.literal("a"), ... }), ...]).
- Intersection:
z.intersection(A, B) or A.and(B).
Refinements and transform
- .refine(fn, message?): Custom validation; keep type unchanged.
- .superRefine: Multiple issues or async; push to
ctx.
- .transform(fn): Change output type. Input and output can differ; use
z.input<typeof schema> and z.output<typeof schema> (or z.infer for output).
- .pipe(otherSchema): Chain validation/transform (e.g. string → number via
z.string().pipe(z.coerce.number())).
Parsing and errors
- .parse(input): Returns data or throws ZodError.
- .safeParse(input): Returns
{ success: true, data } or { success: false, error: ZodError }.
- ZodError:
error.issues (array of { path, message, code }), error.format() for nested shape.
- Async:
.parseAsync / .safeParseAsync for schemas with async refinements or transforms.
Type inference
- z.infer: Output type (after transforms/defaults).
- z.input: Input type (before transforms; useful when input ≠ output).
- z.output: Same as
z.infer for output type.
const S = z.string().transform((s) => s.length);
type In = z.input<typeof S>; // string
type Out = z.output<typeof S>; // number
Integration: React Hook Form
Use @hookform/resolvers with zodResolver:
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
const formSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormValues = z.infer<typeof formSchema>;
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: { email: "", password: "" },
});
Best practices
- Prefer strict schemas (no extra keys) for API boundaries; use .passthrough() only when you need to forward unknown keys.
- Use .default() for optional fields with a default value.
- For API/env parsing, use safeParse and handle errors; show user-friendly messages from
error.issues.
- Use discriminated unions for variant payloads (e.g. events by
type).
- Coerce only when safe:
z.coerce.number(), z.coerce.boolean(), or custom .transform().
Common mistakes
- Forgetting strict: Keep TypeScript
strict: true; Zod works best with it.
- Input vs output: After
.transform(), use z.input<> / z.output<> if the type seen by callers differs.
- parse in hot path: Prefer validating once at boundaries (e.g. API handler, form submit) rather than on every render.
- Overly broad schema: Prefer specific types (e.g.
.email(), .min()) instead of plain z.string() when the domain has rules.
Additional resources
- reference.md — Official Zod docs links, API sections (primitives, objects, strings, numbers, refinements, etc.), Zod Mini, ecosystem.
- Official: https://zod.dev — Introduction, API, basics, ecosystem.
- API (Zod 4): https://zod.dev/api — Full schema types and methods.
- LLMs / index: https://zod.dev/llms.txt — Structured doc for tools/agents.
1---2name: zod3description: Define and use Zod schemas for TypeScript-first validation: primitives, objects, arrays, unions, refinements, transform, z.infer, parse, safeParse. Use when validating input, parsing API data, form validation with react-hook-form, or when the user mentions Zod, schema validation, or z.infer.4---5
6# Zod
7
8## Overview
9
10Zod is a **TypeScript-first schema validation library** with **static type inference**. Define a schema once; you get both runtime validation and a TypeScript type (via `z.infer`). Zero external dependencies; works in Node and browsers. Ideal for forms (e.g. with react-hook-form), API parsing, env vars, and any untrusted input.
11
12**Requirements**: TypeScript 5.5+ recommended; enable **`strict`** in `tsconfig.json`.
13
14**Install**: Add `zod` via your package manager. See [zod.dev](https://zod.dev).
15
16---
17
18## Quick start
19
20```ts
21import { z } from "zod";
22
23const User = z.object({
24 name: z.string(),
25 age: z.number().optional(),
26});
27
28type User = z.infer<typeof User>;
29// { name: string; age?: number }
30
31const data = User.parse(input); // throws ZodError if invalid
32const result = User.safeParse(input); // { success: true, data } | { success: false, error }
33```
34
35---
36
37## Primitives and common types
38
39| Schema | Type | Notes |
40|--------|------|--------|
41| `z.string()` | string | |
42| `z.number()` | number | |
43| `z.boolean()` | boolean | |
44| `z.bigint()` | bigint | |
45| `z.date()` | Date | |
46| `z.undefined()` | undefined | |
47| `z.null()` | null | |
48| `z.void()` | void | |
49| `z.any()` | any | |
50| `z.unknown()` | unknown | |
51| `z.never()` | never | |
52| `z.literal("x")` | literal | |
53| `z.enum(["a", "b"])` | union of literals | |
54| `z.string().email()` | string | email format |
55| `z.string().url()` | string | URL |
56| `z.string().uuid()` | string | UUID |
57| `z.string().datetime()` | string | ISO datetime |
58| `z.number().int()` | number | integer |
59| `z.number().min(n).max(m)` | number | range |
60| `z.boolean()` / `z.string().transform(...)` | stringbool | "true"/"false" coercion |
61
62Optional: `.optional()` → `T | undefined`. Nullable: `.nullable()` → `T | null`. Nullish: `.nullish()` → `T | null | undefined`. Default: `.default(value)`.
63
64---
65
66## Objects
67
68```ts
69const Schema = z.object({
70 name: z.string(),
71 age: z.number().optional(),
72 tags: z.array(z.string()).default([]),
73});
74
75type Schema = z.infer<typeof Schema>;
76```
77
78- **.shape**: `Schema.shape.name` (access field schema).
79- **.keyof()**: `Schema.keyof()` → enum of keys.
80- **.extend({ ... })**: Add or override keys.
81- **.pick({ name })** / **.omit({ age })**: Subset of keys.
82- **.partial()** / **.required({ name })**: Optionalize or require.
83- **.strict()**: No extra keys (default in z.object). **z.strictObject** / **z.looseObject** for behavior variants.
84- **.catchall(z.string())**: Allow extra keys with a schema.
85- **Nested**: `z.object({ user: z.object({ name: z.string() }) })`.
86- **Recursive**: `z.lazy(() => Category)` for self-referential structures.
87
88---
89
90## Arrays and tuples
91
92- **Arrays**: `z.array(z.string())`, `z.string().array()`.
93- **Tuples**: `z.tuple([z.string(), z.number()])` — fixed length and types.
94- **Non-empty**: `.min(1)` or dedicated helpers if available.
95
96---
97
98## Unions and intersections
99
100- **Union**: `z.union([z.string(), z.number()])` or `z.string().or(z.number())`.
101- **Discriminated union**: Use a common key (e.g. `type: "a"`) and `z.discriminatedUnion("type", [z.object({ type: z.literal("a"), ... }), ...])`.
102- **Intersection**: `z.intersection(A, B)` or `A.and(B)`.
103
104---
105
106## Refinements and transform
107
108- **.refine(fn, message?)**: Custom validation; keep type unchanged.
109- **.superRefine**: Multiple issues or async; push to `ctx`.
110- **.transform(fn)**: Change output type. Input and output can differ; use `z.input<typeof schema>` and `z.output<typeof schema>` (or `z.infer` for output).
111- **.pipe(otherSchema)**: Chain validation/transform (e.g. string → number via `z.string().pipe(z.coerce.number())`).
112
113---
114
115## Parsing and errors
116
117- **.parse(input)**: Returns data or throws **ZodError**.
118- **.safeParse(input)**: Returns `{ success: true, data }` or `{ success: false, error: ZodError }`.
119- **ZodError**: `error.issues` (array of `{ path, message, code }`), `error.format()` for nested shape.
120- **Async**: `.parseAsync` / `.safeParseAsync` for schemas with async refinements or transforms.
121
122---
123
124## Type inference
125
126- **z.infer<typeof schema>**: Output type (after transforms/defaults).
127- **z.input<typeof schema>**: Input type (before transforms; useful when input ≠ output).
128- **z.output<typeof schema>**: Same as `z.infer` for output type.
129
130```ts
131const S = z.string().transform((s) => s.length);
132type In = z.input<typeof S>; // string
133type Out = z.output<typeof S>; // number
134```
135
136---
137
138## Integration: React Hook Form
139
140Use **@hookform/resolvers** with **zodResolver**:
141
142```ts
143import { zodResolver } from "@hookform/resolvers/zod";
144import { useForm } from "react-hook-form";
145import { z } from "zod";
146
147const formSchema = z.object({
148 email: z.string().email(),
149 password: z.string().min(8),
150});
151
152type FormValues = z.infer<typeof formSchema>;
153
154const form = useForm<FormValues>({
155 resolver: zodResolver(formSchema),
156 defaultValues: { email: "", password: "" },
157});
158```
159
160---
161
162## Best practices
163
164- Prefer **strict** schemas (no extra keys) for API boundaries; use **.passthrough()** only when you need to forward unknown keys.
165- Use **.default()** for optional fields with a default value.
166- For API/env parsing, use **safeParse** and handle errors; show user-friendly messages from `error.issues`.
167- Use **discriminated unions** for variant payloads (e.g. events by `type`).
168- Coerce only when safe: `z.coerce.number()`, `z.coerce.boolean()`, or custom `.transform()`.
169
170---
171
172## Common mistakes
173
174- **Forgetting strict**: Keep TypeScript `strict: true`; Zod works best with it.
175- **Input vs output**: After `.transform()`, use `z.input<>` / `z.output<>` if the type seen by callers differs.
176- **parse in hot path**: Prefer validating once at boundaries (e.g. API handler, form submit) rather than on every render.
177- **Overly broad schema**: Prefer specific types (e.g. `.email()`, `.min()`) instead of plain `z.string()` when the domain has rules.
178
179---
180
181## Additional resources
182
183- [reference.md](reference.md) — Official Zod docs links, API sections (primitives, objects, strings, numbers, refinements, etc.), Zod Mini, ecosystem.
184- **Official**: https://zod.dev — Introduction, API, basics, ecosystem.
185- **API (Zod 4)**: https://zod.dev/api — Full schema types and methods.
186- **LLMs / index**: https://zod.dev/llms.txt — Structured doc for tools/agents.