Skip to content

Generate a Zod schema from JSON

Turn a JSON sample into a Zod schema with nested z.object() definitions, .optional() and .nullable() modifiers and an inferred TypeScript type.

  • Runs in your browser
  • Files are never uploaded
  • Free, no account
JSON to Zod schema
Paste a real API response and get types that match its actual shape: nested objects become named types, keys missing from some array elements become optional, and values that vary become unions.

The name given to the outermost type.

Your JSON never leaves this tab — check it yourself—

Starting the network probe…

The counter above wraps fetch, XMLHttpRequest and navigator.sendBeacon in this tab and counts every call. It does not block anything — it just reports.

Two ways to verify independently

  1. Turn off Wi-Fi or enable airplane mode, then use the tool. It keeps working, because the page is already fully loaded.
  2. Open your browser devtools, switch to the Network tab, clear it, and run the tool. No new row appears.

Why this matters

In 2025 researchers found that popular online formatters had retained tens of thousands of user-submitted snippets — JSONFormatter.org and CodeBeautify together exposed roughly 80,000 saved documents, many containing live cloud credentials, private keys and internal database connection strings, because “format this” quietly meant “upload this”. A separate incident exposed roughly 3.5 million user PDFs on a document-conversion service. The only structural fix is not sending the data in the first place: this site is served under a Content-Security-Policy whose connect-src contains no third-party origin, so a request off this device cannot be made even by accident.

Why a schema beats an interface at a boundary

A TypeScript interface is a compile-time assertion. Write const user = await res.json() as User and the compiler believes you completely; at runtime, nothing is checked. When the API drops a field, the failure surfaces as Cannot read properties of undefined somewhere unrelated, minutes or days later.

A Zod schema is a runtime check that also produces the static type. userSchema.parse(data) throws immediately, at the boundary, with a path to the offending field — and z.infer<typeof userSchema> gives you the same User type you would have written by hand. One definition, both guarantees.

What the generator emits

  • One z.object({…}) per distinct nested shape, named after the key that holds it.
  • .optional() on keys that were absent from some records.
  • .nullable() where a null was observed alongside another type.
  • z.number().int() for values that were whole numbers, z.number() where any sample was fractional.
  • z.array(…) with the merged element schema, or z.unknown() if the array was empty.
  • A final z.infer type alias for the root.

Child schemas are declared before the schemas that reference them, so the file compiles without reordering. Only zod itself is imported.

Tightening the generated schema

What you get is structurally accurate and semantically loose, because no sample can reveal intent. The refinements worth adding by hand:

GeneratedBetterWhy
z.string() for an emailz.string().email()Catches malformed addresses at the boundary
z.string() for a statusz.enum(['open','closed','pending'])Turns an unexpected value into an error, and narrows the type
z.string() for a timestampz.string().datetime() or z.coerce.date()Validates the format, or gives you a Date
z.number() for a quantityz.number().int().nonnegative()Encodes the real constraint
z.object({…}).strict()Rejects unknown keys instead of stripping them

Handling failures well

Prefer safeParse over parse at a network boundary. It returns a discriminated union rather than throwing, so you can log result.error.issues — which carries the exact path, expected type and received type — and show the user something useful instead of a stack trace. In a React app, z.treeifyError maps the issues onto form fields directly.

Schema drift is the real payoff

The value of a runtime schema is not catching your own bugs; it is catching someone else’s change. A provider renames created_at to createdAt, or starts returning an ID as a number, or drops a field they thought nobody used. With a schema at the boundary you get one clear error naming the field. Without one, you get a support ticket about a blank column.

Questions

Why generate a Zod schema instead of a TypeScript interface?

An interface disappears at runtime; a Zod schema is a runtime check. Parsing an API response through a schema catches a contract change at the boundary instead of as an undefined-property crash three functions later — and `z.infer` still gives you the static type for free.

What does the generated code import?

A single `import { z } from 'zod'`. Zod itself is not bundled by this page and is not needed to generate — you install it in your own project.

Are integers distinguished from floats?

Yes. A value that was a whole number in the sample becomes `z.number().int()`. If the same field is sometimes fractional across your records, the merged type drops `.int()`. Review the strictness before shipping — a field that happened to be integral in your sample may not always be.

How do I make the schema stricter?

Add the constraints the generator cannot infer: `.email()`, `.uuid()`, `.min()`, `.max()`, `.datetime()`, and `z.enum([...])` where a string only ever takes a few values. The generated file is a starting point, not a specification.