ZodError flatten vs format
Compare ZodError flatten() and format() with the Zod 4 helpers z.flattenError() and z.treeifyError(), and choose the right error shape for forms, API responses, and nested data.
Quick answer: flatten or format?
Use a flatten shape for flat forms where you want `fieldErrors[name]` message arrays, and a treeify/format shape for nested data where errors must follow deep paths. In Zod 4, prefer the top-level `z.flattenError()` and `z.treeifyError()` helpers; the `error.flatten()` and `error.format()` methods still work but are legacy.
z.flattenError(): field errors for flat objects
Flatten produces a single level of `fieldErrors` keyed by the top-level property name, plus `formErrors` for issues that are not tied to a field. This maps cleanly onto form inputs, which is why it is the default choice for form validation.
import * as z from "zod";
const SignupSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
const result = SignupSchema.safeParse(input);
if (!result.success) {
const flat = z.flattenError(result.error);
// {
// formErrors: [],
// fieldErrors: {
// email: ["Invalid email address"],
// password: ["String must contain at least 8 character(s)"]
// }
// }
}z.treeifyError(): nested errors for deep data
Treeify mirrors the schema structure so errors stay attached to deep paths. Each node exposes an `errors` array, and object or array nodes expose `properties` and `items` branches. Use it when flatten would collapse nested paths you need to render.
const OrderSchema = z.object({
customer: z.object({
email: z.string().email(),
}),
items: z.array(z.object({ sku: z.string().min(1) })),
});
const result = OrderSchema.safeParse(input);
if (!result.success) {
const tree = z.treeifyError(result.error);
// tree.properties?.customer?.properties?.email?.errors -> ["Invalid email address"]
// tree.properties?.items?.items?.[0]?.properties?.sku?.errors -> ["..."]
}Legacy error.flatten() and error.format()
The instance methods still exist on ZodError for compatibility. `error.flatten()` returns the same shape as `z.flattenError()`, and `error.format()` returns a nested tree similar to `z.treeifyError()` but using `_errors` arrays. New code should prefer the top-level helpers, which are the maintained path going forward.
// Legacy (still works in Zod 4) const flat = result.error.flatten(); const nested = result.error.format(); // nodes use _errors: string[] // Preferred in Zod 4 const flat2 = z.flattenError(result.error); const nested2 = z.treeifyError(result.error);
Which shape fits forms vs APIs
The right helper depends on how the consumer renders errors. Match the output shape to the UI or API contract so you do not reshape the data twice.
- Forms with flat fields: use `z.flattenError()` and read `fieldErrors[name]`.
- Nested objects or arrays of objects: use `z.treeifyError()` to keep errors on deep paths.
- Human-readable logs or a single message string: use `z.prettifyError(error)`.
- Public API responses: map issues yourself from `error.issues` so you control wording and never leak internal paths.
Migrating Zod 3 error handling to Zod 4
If you are upgrading, the methods you already call keep working, so migration can be incremental. Move to the top-level helpers as you touch each error path, and note that `treeifyError` uses `errors` arrays where `format()` used `_errors`.
- Replace `error.flatten()` with `z.flattenError(error)` (identical shape).
- Replace `error.format()` with `z.treeifyError(error)` and update `_errors` reads to `errors`.
- Generate a starter schema with JSON to Zod, then choose the error helper at the boundary that handles unknown input.
ZodError error helpers in Zod 4
| Helper | Output shape | Use when |
|---|---|---|
| z.flattenError(error) | `{ formErrors: string[], fieldErrors: Record<string, string[]> }` — one level deep. | You validate a flat object such as a form and want field-keyed message arrays. |
| z.treeifyError(error) | A nested tree mirroring the schema, with `errors` arrays and `properties` / `items` branches. | You validate nested objects or arrays and need errors mapped to deep paths. |
| error.flatten() / error.format() | The same shapes as above, but as legacy instance methods kept for Zod 3 compatibility. | You are migrating existing Zod 3 code and have not switched to the top-level helpers yet. |
Use FrameworkKit to generate the starter code, then review the output before shipping it in production.
Generate with the JSON to Zod schema generatorZod validation resources
JSON to Zod converter
Convert sample payloads into copy-ready Zod schemas and inferred TypeScript types in the browser.
JSON to Zod schema examples
Open realistic API and product payload examples before adapting the JSON to Zod schema output.
TypeScript to Zod Converter
Convert TypeScript interfaces and type aliases into Zod schemas when the source shape already lives in code.
Zod to JSON Schema Converter
Use the canonical online converter when an existing Zod schema needs JSON Schema, AJV, or OpenAPI output.
Validate API responses with Zod
Validate API responses, request bodies, and fetch boundaries with Zod schemas in TypeScript.
Zod parse vs safeParse
Choose between parse, safeParse, parseAsync, and safeParseAsync for TypeScript validation flows.
Zod safeParse
Validate unknown data without throwing, narrow the result type, and format safeParse errors.
Zod safeParse error messages
Format safeParse failures, field errors, and API validation responses in TypeScript.
Zod refine vs superRefine
Add custom validation, async checks, and field-level error paths after generating a starter schema.
Zod nativeEnum in Zod 4
Migrate z.nativeEnum patterns to z.enum, validate TypeScript enums, and avoid enum value mistakes.
Use JSON to Zod for form validation
Start from a submitted form payload, then add business rules such as email, length, and enum checks.
Zod to JSON Schema for OpenAPI
Publish Zod API schemas as OpenAPI-compatible contracts when teams need portable documentation.
Zod coerce for query params
Turn string query params and form fields into numbers, dates, and booleans, and avoid the coerce.boolean trap.
Zod email validation (z.email)
Validate emails with the top-level z.email() in Zod 4, add custom messages, and migrate off z.string().email().
Zod transform and pipe
Reshape validated data with transform, chain a second check with pipe, and use z.codec for reversible conversions.
Zod vs JSON Schema
Choose between TypeScript-first runtime validation and portable schema contracts.
Zod vs Yup vs Valibot
Compare TypeScript validation libraries for API boundaries, forms, server actions, and bundle tradeoffs.
FAQ
What is the difference between flatten and format in Zod?
Flatten returns a single level of `fieldErrors` plus `formErrors`, which suits flat forms. Format (and its Zod 4 successor `z.treeifyError`) returns a nested tree that mirrors the schema, which suits deeply nested data.
Are error.flatten() and error.format() deprecated in Zod 4?
They still work for compatibility, but Zod 4 introduces top-level `z.flattenError()` and `z.treeifyError()` as the maintained approach. New code should prefer the helpers.
How do I get field errors for a form?
Call `z.flattenError(result.error)` after a failed safeParse and read `fieldErrors[fieldName]`, which is an array of messages for that input.
How do I map nested validation errors?
Use `z.treeifyError(result.error)` and walk the `properties` and `items` branches; each node has an `errors` array for the messages at that path.
Related tools
TypeScript to Zod Converter
Convert TypeScript interfaces and type aliases into Zod schemas with inferred types in your browser.
Zod to JSON Schema Converter
Use a free browser-only online converter to turn Zod 4 schemas into JSON Schema for Draft 2020-12, Draft 7, AJV, or OpenAPI-compatible output.
OpenAPI to Zod
Turn OpenAPI schemas into Zod validators and lightweight typed fetch clients.