zod flatten vs format

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.

Open the JSON to Zod converter

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

HelperOutput shapeUse 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 generator

Zod 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