|
| 1 | +--- |
| 2 | +name: use-zod |
| 3 | +description: 'Answer questions about the Zod schema validation library and help build schemas, parsers, refinements, transforms, codecs, and error formatters. Use when developers: (1) ask about Zod APIs like `z.object`, `z.string`, `z.array`, `z.union`, `z.discriminatedUnion`, `parse`, `safeParse`, `z.infer`; (2) define request/response/form schemas in TypeScript; (3) handle `ZodError` or customize error messages; (4) migrate between Zod v3 and v4 (entry-point split, `formatError` → `treeifyError`/`prettifyError`, unified `error` param replacing `message`/`errorMap`). Triggers on: "zod", "z.object", "z.string", "z.array", "z.union", "z.infer", "z.input", "z.output", "ZodError", "$ZodError", "safeParse", "parseAsync", "z.codec", "treeifyError", "prettifyError", "flattenError", "discriminatedUnion", "zod/v4", "zod/v3", "zod/mini", "z.coerce", "superRefine".' |
| 4 | +--- |
| 5 | + |
| 6 | +## Prerequisites |
| 7 | + |
| 8 | +Verify the `ask` CLI is available (`which ask`). It is the primary tool for reading the exact version installed in this project — it resolves the version from the lockfile, fetches docs/source once, and caches them at `~/.ask/`. If `ask` is not installed, fall back to `node_modules/zod/` and the official site at https://zod.dev (which tracks the latest published v4, not necessarily the installed version). |
| 9 | + |
| 10 | +Before writing Zod code, verify the installed version and entry points: |
| 11 | + |
| 12 | +```bash |
| 13 | +# installed version — drives everything below |
| 14 | +cat node_modules/zod/package.json 2>/dev/null | jq -r .version |
| 15 | + |
| 16 | +# subpath exports — confirms which import paths resolve (zod, zod/mini, zod/v3, zod/v4) |
| 17 | +cat node_modules/zod/package.json 2>/dev/null | jq '.exports | keys' |
| 18 | +``` |
| 19 | + |
| 20 | +If `zod` is missing, install only what the task requires: |
| 21 | + |
| 22 | +```bash |
| 23 | +# v4 (current default since zod@4.0.0) |
| 24 | +pnpm add zod # or: bun add zod / npm i zod / yarn add zod |
| 25 | + |
| 26 | +# pin to v3 only when the project explicitly requires it |
| 27 | +pnpm add zod@^3 |
| 28 | +``` |
| 29 | + |
| 30 | +Detect the package manager from the lockfile (`pnpm-lock.yaml` → pnpm, `bun.lockb` → bun, `package-lock.json` → npm, `yarn.lock` → yarn). |
| 31 | + |
| 32 | +## Critical: Do Not Trust Internal Knowledge |
| 33 | + |
| 34 | +Zod 4 (released 2025) was a major rewrite. Many APIs that were canonical in v3 are now deprecated, renamed, or removed. Examples that are commonly miswritten from training data: |
| 35 | + |
| 36 | +- `err.format()` / `err.flatten()` (v3 instance methods) — in v4 these are top-level functions: `z.treeifyError(err)` / `z.flattenError(err)`. `z.formatError()` exists but is **deprecated** in favour of `z.treeifyError()`. |
| 37 | +- `z.string({ message, errorMap })` (v3) — v4 unifies these into a single `error` param: `z.string({ error: "Bad!" })` or `z.string({ error: (iss) => "..." })`. |
| 38 | +- `.superRefine()` is **still the recommended** v4 API for multi-issue refinements. `.check()` exists as a lower-level, more verbose alternative for performance-sensitive paths — not as a replacement. |
| 39 | +- `error instanceof z.ZodError` — works for the regular `zod` package; for `zod/mini` use `error instanceof z.core.$ZodError` (the parent class). |
| 40 | +- Codecs (`z.codec(...)`) — only exist in `zod@4.1+`. Do not suggest them on v3 or earlier 4.x. |
| 41 | + |
| 42 | +When working with Zod: |
| 43 | + |
| 44 | +1. Resolve the installed version against the local checkout with `ask` (see [Finding Documentation](#finding-documentation) below). |
| 45 | +2. Verify every API name, method signature, and option shape against the source or bundled `.d.ts` before generating code. Never invent method names. |
| 46 | +3. Cross-reference upstream docs **at the matching version pin** ([`references/versions.md`](references/versions.md) has the v4.3.6 / v3.25.76 links) — not `main`, which tracks the latest release. |
| 47 | +4. Run typecheck after every change. Zod schemas are heavily inferred and silent type drift is rare. |
| 48 | +5. Surface API trade-offs to the user instead of silently emitting either pattern (e.g. `.superRefine` is recommended in v4; `.check()` is a lower-level alternative for performance-sensitive paths — clarify when relevant). |
| 49 | + |
| 50 | +If documentation cannot be found locally or remotely to back an answer, say so explicitly. |
| 51 | + |
| 52 | +## Finding Documentation |
| 53 | + |
| 54 | +Resolve the source checkout and docs directory once with `ask`; reuse the paths across reads: |
| 55 | + |
| 56 | +```bash |
| 57 | +SRC=$(ask src zod) # checkout root |
| 58 | +DOCS=$(ask docs zod | head -n1) # candidate docs dir |
| 59 | +``` |
| 60 | + |
| 61 | +Both pin to the version in the project's lockfile. To inspect a specific version regardless of the project, append `@version`: |
| 62 | + |
| 63 | +```bash |
| 64 | +SRC_V4=$(ask src zod@4.3.6) |
| 65 | +SRC_V3=$(ask src zod@3.25.76) |
| 66 | +``` |
| 67 | + |
| 68 | +### Read the README and docs content |
| 69 | + |
| 70 | +```bash |
| 71 | +cat "$DOCS/README.md" |
| 72 | +ls "$SRC/packages/docs/content" # v4 docs source (mdx) |
| 73 | +cat "$SRC/packages/docs/content/api.mdx" # full API reference |
| 74 | +cat "$SRC/packages/docs/content/error-formatting.mdx" |
| 75 | +cat "$SRC/packages/docs/content/error-customization.mdx" |
| 76 | +cat "$SRC/packages/docs/content/codecs.mdx" # v4.1+ only |
| 77 | +``` |
| 78 | + |
| 79 | +### Verify a symbol exists in the installed version |
| 80 | + |
| 81 | +```bash |
| 82 | +# top-level functions (v4): treeifyError, prettifyError, flattenError, codec, config |
| 83 | +rg -n "^export (function|const) (treeifyError|prettifyError|flattenError|codec|config)\\b" "$SRC/packages/zod/src" |
| 84 | + |
| 85 | +# instance methods on schemas |
| 86 | +rg -n "(\\.refine|\\.check|\\.superRefine|\\.overwrite|\\.transform|\\.parseAsync)\\b" "$SRC/packages/zod/src" |
| 87 | + |
| 88 | +# subpath exports |
| 89 | +cat "$SRC/packages/zod/package.json" | jq '.exports | keys' |
| 90 | +``` |
| 91 | + |
| 92 | +### Find canonical example shapes (tests are the most reliable source) |
| 93 | + |
| 94 | +```bash |
| 95 | +fd -e test.ts . "$SRC/packages/zod/tests" |
| 96 | +rg -n "discriminatedUnion|z\\.codec|treeifyError" "$SRC/packages/zod/tests" |
| 97 | +``` |
| 98 | + |
| 99 | +### Fallback when `ask` is unavailable |
| 100 | + |
| 101 | +```bash |
| 102 | +SRC=./node_modules/zod |
| 103 | +ls $SRC/dist |
| 104 | +rg "treeifyError" $SRC/dist # confirm v4 helpers shipped in this build |
| 105 | +cat $SRC/package.json | jq .version |
| 106 | +``` |
| 107 | + |
| 108 | +Use https://zod.dev only to cross-reference — it always tracks the latest published v4. |
| 109 | + |
| 110 | +## Version detection — branch v4 vs v3 paths |
| 111 | + |
| 112 | +```bash |
| 113 | +node -e "const v=require('zod/package.json').version; console.log(v.startsWith('4.')?'v4':v.startsWith('3.')?'v3':v)" |
| 114 | +``` |
| 115 | + |
| 116 | +| Detected | Default import | Errors API | Refinement API | Codecs | |
| 117 | +| --- | --- | --- | --- | --- | |
| 118 | +| v4 (≥4.0.0) | `import * as z from "zod"` | `z.treeifyError`, `z.prettifyError`, `z.flattenError` | `.refine()`, `.superRefine()`, `.check()` (low-level) | `z.codec()` (4.1+) | |
| 119 | +| v3 (≥3.0, <4.0) | `import { z } from "zod"` | `err.format()`, `err.flatten()` | `.refine()`, `.superRefine()` | — | |
| 120 | +| v3.25.x bridge | `import * as z from "zod/v4"` opt-in to v4 alongside v3 | per the chosen path | per the chosen path | — | |
| 121 | + |
| 122 | +`zod@3.25` shipped both v3 (default) and v4 (under `zod/v4`) in the same package to ease migration. From `zod@4.0.0` onward, the root export is v4 and `zod/v3` is the back-compat path. See [`references/versions.md`](references/versions.md). |
| 123 | + |
| 124 | +## Entry points (v4) |
| 125 | + |
| 126 | +| Import | Use when | |
| 127 | +| --- | --- | |
| 128 | +| `import * as z from "zod"` | Default. Standard ergonomic API with chainable methods (`z.string().min(5)`). | |
| 129 | +| `import * as z from "zod/mini"` | Bundle-size-sensitive frontend code. Functional API: `z.string().check(z.minLength(5))`. ~64% smaller for trivial schemas. | |
| 130 | +| `import * as z from "zod/v3"` | Legacy code on v3 that you can't migrate yet, while consuming a `zod@4` package. | |
| 131 | +| `import * as z from "zod/v4-mini"` (within `zod@3.25`) | Forward-compat path for projects pinned to v3 that want to start adopting Mini. | |
| 132 | + |
| 133 | +`zod/mini` and `zod` interop: schemas from one cannot be passed to the other's parse functions. Pick one per project unless you have a deliberate reason to mix. |
| 134 | + |
| 135 | +## Authoring schemas |
| 136 | + |
| 137 | +Concise cookbook of common patterns, each tagged with the version it applies to: [`references/schemas.md`](references/schemas.md). |
| 138 | + |
| 139 | +Rules of thumb: |
| 140 | + |
| 141 | +- **`z.object` is non-strict by default** — extra keys are stripped. Use `z.strictObject({...})` to reject extra keys, or `.passthrough()` (v3) / `.loose()` (v4) to preserve them. |
| 142 | +- **`.optional()` vs `.nullable()` vs `.nullish()`** — `optional` allows `undefined`, `nullable` allows `null`, `nullish` allows both. |
| 143 | +- **Always export the type** with `z.infer<typeof Schema>`. Use `z.input` and `z.output` separately when the schema transforms (input ≠ output, e.g. `z.string().transform(s => s.length)`). |
| 144 | +- **Discriminated unions need a literal discriminator** — `z.discriminatedUnion("type", [...])` is dramatically faster and produces better error messages than `z.union(...)` when shapes share a tag field. |
| 145 | +- **Recursive schemas** use different patterns per version: v4 uses object property **getters** (`get children() { return z.array(Self); }`); v3 uses `z.lazy(() => Schema)` plus an explicit `z.ZodType<Node>` annotation. See [`references/schemas.md`](references/schemas.md#recursive-schemas). |
| 146 | + |
| 147 | +## Parsing & error handling |
| 148 | + |
| 149 | +Concise reference: [`references/parsing-and-errors.md`](references/parsing-and-errors.md). |
| 150 | + |
| 151 | +Quick map: |
| 152 | + |
| 153 | +- `parse(input)` — throws on invalid; returns typed deep clone. |
| 154 | +- `safeParse(input)` — returns `{ success: true, data } | { success: false, error: ZodError }`. |
| 155 | +- `parseAsync` / `safeParseAsync` — required when the schema contains async refinements, transforms, or codecs. |
| 156 | +- `try { ... } catch (e) { if (e instanceof z.ZodError) e.issues }` — every error has an `.issues` array of `{ code, path, message, expected?, ... }`. |
| 157 | + |
| 158 | +The `formatError` → `treeifyError` rename is the single most common source of broken v3-era examples. Surface it whenever rewriting v3 error-handling code. |
| 159 | + |
| 160 | +## When typecheck or runtime fails |
| 161 | + |
| 162 | +Before searching source code, check the most common Zod failure modes: |
| 163 | + |
| 164 | +1. **`Invalid input: expected X`** with no `path` — top-level shape mismatch; verify the schema matches the expected outer type. |
| 165 | +2. **`Cannot read property 'parseAsync' of undefined`** — usually an import-path mismatch (`zod` vs `zod/mini`); methods on Mini schemas live on top-level functions instead. |
| 166 | +3. **`Type 'ZodError' is not assignable to type '$ZodError'`** — mixing `zod` and `zod/mini` schemas in the same code path. |
| 167 | +4. **Async refinement throws "Synchronous parsing not supported"** — switch the call site from `parse` to `parseAsync` (or `safeParse` to `safeParseAsync`). |
| 168 | +5. **Custom error not surfacing** — confirm you're using the unified `error` param (v4) and not the legacy `message`/`errorMap` shape (v3). |
| 169 | + |
| 170 | +If the symptom is not listed, resolve the source and grep the error string: |
| 171 | + |
| 172 | +```bash |
| 173 | +rg -n "error string fragment" "$(ask src zod)/packages/zod/src" |
| 174 | +# fallback: rg -n "error string fragment" node_modules/zod/dist |
| 175 | +``` |
| 176 | + |
| 177 | +## References |
| 178 | + |
| 179 | +- [`references/versions.md`](references/versions.md) — entry points, version detection, v3 ↔ v4 API rename cheatsheet, links to upstream docs at version pins (v4.3.6, v3.25.76) |
| 180 | +- [`references/schemas.md`](references/schemas.md) — primitives, objects, arrays, unions, discriminated unions, recursion, refinements, transforms, codecs (v4.1+) — each example tagged `// v4`, `// v3`, or `// both` |
| 181 | +- [`references/parsing-and-errors.md`](references/parsing-and-errors.md) — `parse` vs `safeParse` vs `parseAsync`, `ZodError` shape, `treeifyError`/`prettifyError`/`flattenError` (v4), `format`/`flatten` (v3), error customization |
0 commit comments