Zod Error Formatting and Performance#
Zod emphasizes completeness and correctness in error reporting. When validation fails, Zod produces a $ZodError (the ZodError class in the zod package is a subclass with additional convenience methods) that contains an .issues array β each issue includes a human-readable message, an error code, a path, and structured metadata about the failure .
Error Formatting Utilities#
Zod provides several utilities to transform a ZodError into a more usable shape. See the Formatting errors reference for full examples.
z.treeifyError(error)#
Converts the flat .issues array into a nested object that mirrors the schema . Each node has an errors array for messages at that path, plus properties (for objects) and items (for arrays) to traverse deeper. Use optional chaining (?.) when accessing nested nodes.
const tree = z.treeifyError(result.error);
tree.properties?.username?.errors; // errors at username
tree.properties?.favoriteNumbers?.items?.[1]?.errors; // errors at index 1
z.formatError() β present in earlier docs β is now deprecated; use z.treeifyError() instead .
z.flattenError(error)#
Designed for flat (one-level-deep) schemas β the most common case. Returns { formErrors: string[], fieldErrors: { [key: string]: string[] } } . formErrors holds top-level errors (path []); fieldErrors maps each field key to its error messages. Nested validation failures for deeply-nested schemas are collapsed to their first-level key.
z.prettifyError(error)#
Returns a human-readable string formatted with β bullets and β at <path> annotations . Intended for logging and debugging, not for surfacing to end users programmatically.
β Unrecognized key: "extraKey"
β Invalid input: expected string, received number
β at username
β Invalid input: expected number, received string
β at favoriteNumbers[1]
Error Customization Quick Reference#
Errors can be customized at multiple levels; the precedence order from highest to lowest is :
- Check-level β
z.string().min(5, "Too short!") - Schema-level β
z.string("Not a string!") - Per-parse β
schema.parse(data, { error: iss => "..." }) - Global error map β
z.config({ customError: iss => "..." }) - Locale error map β
z.config(z.locales.en())
The error param accepts a string or a function receiving an issue context object (iss) with code, input, path, inst, and schema . Return undefined to defer to the next level in the chain.
For internationalization, Zod ships built-in locales (Arabic, French, Japanese, and 70+ others) exported from zod/v4/locales . Regular zod loads the en locale automatically; zod/mini loads none by default and uses "Invalid input" as the fallback.
AOT Compilation Performance (z.compile())#
Introduced in Zod 4, z.compile() performs ahead-of-time (AOT) compilation of a schema into a flat, loop-free JavaScript validator via new Function() . This fast-path function is used for valid inputs; on failure it falls back to the standard parser to provide full error detail. Results and error messages are identical to the uncompiled schema.
Speedups#
Benchmarks show speedups that scale with schema complexity :
| Schema | Speedup |
|---|---|
| object, 5 keys | 1.8Γ |
| object, 20 keys | 5.0Γ |
| object, 50 keys | 10.2Γ |
| tuple, 10 items | 3.7Γ |
On the Moltar benchmark (parseSafe category), Zod 4 compiled reaches 47.5 M ops/sec, compared to 11.6 M for uncompiled Zod 4 and 1.2 M for Zod 3 . In the assertLoose category Zod 4 compiled reaches 60.6 M ops/sec, competitive with typia (74.9 M) and arktype (66.2 M).
Invalid inputs pay for both the fast path and the fallback, so compilation mostly speeds up the success path. Failures are dominated by the fallback and see minimal benefit .
Opt-in Methods#
z.compile(schema) β compiles a single schema; returns a compiled copy. Compile after all chaining (.refine(), .extend(), etc.) since derived schemas are uncompiled .
import "zod/compile" β enables global auto-compilation. Every schema constructed after this import is compiled lazily on first parse. Suitable for applications; not recommended for libraries. Can also be set as a Node.js CLI flag: node --import zod/compile app.js .
Limitations#
The following features cannot be compiled β z.compile() silently returns the original schema:
asyncrefinements, transforms, or checksz.xor(), recursive schemas,z.coerce.*- Checks with a custom
whencondition .catch()given a callback (constant.catch(value)compiles fine)
Pass { strict: true } to throw instead of silently falling back β useful to verify hot-path schemas actually compiled (ZodCompileAsyncError or ZodCompileUnsupportedError) .
CSP / No-eval Environments#
z.compile() uses new Function(), so it is unavailable in CSP/no-eval environments. Set z.config({ jitless: true }) to disable global auto-compilation; calling z.compile() directly still attempts code generation and returns the schema uncompiled if rejected .
Bundle Size Impact#
The compiler adds ~7 KB gzipped (28 KB minified) and is fully tree-shaken when unused :
| Bundle (four-key object) | Without compiler | With compiler |
|---|---|---|
| Zod | 24.1 KB | 31.1 KB |
| Zod Mini | 4.6 KB | 13.2 KB |
Zod vs. Alternatives (Non-TypeScript Environments)#
Zod works in plain JavaScript as well as TypeScript, and its zero-dependency, 2 KB core bundle makes it competitive with alternatives like Joi even in non-TypeScript environments.
On the Moltar benchmark, Zod 4 compiled outperforms yup (121 k ops/sec), Zod 3 (1.2 M), effect (1.7 M), and valibot (1.8 M) by a large margin . Libraries like Joi are not directly benchmarked in Zod's own suite, but the Moltar numbers place Zod 4 compiled near the top of runtime validators.
For backend workloads, bundle size concerns are largely irrelevant: benchmarks show Lambda cold-start time increases only ~0.6 ms for the full 17 KB (gzipped) Zod package . The decision to use Zod Mini over Zod mainly applies to front-end bundles targeting users on slow mobile connections .
Key References#
| Resource | Purpose |
|---|---|
| Formatting errors | treeifyError, flattenError, prettifyError API reference |
| Customizing errors | Error maps, precedence, i18n, locales |
| AOT compilation | z.compile() API, benchmarks, limitations |
| Moltar benchmark | Cross-library throughput comparisons |
| compile-matrix.ts | Internal compile benchmark source |
| compile-scaling.ts | Scaling-by-key-count benchmark source |