Skip to content
100% local

JSON to Zod schema

Generate a Zod validation schema and TypeScript types from a JSON sample, with format and enum detection.

Input
Output

JSON to Zod schema

Paste a JSON sample — an API response, a config file, a database row — and get a ready-to-use Zod schema in TypeScript. Types are inferred per field: primitives map to z.string(), z.number() and z.boolean(), arrays carry their inferred element type, and a value that differs across occurrences becomes a z.union(). A string that looks like an email address, a URL, a UUID or an ISO date gets the matching validator — .email(), .url(), .uuid(), .date() or .datetime() — instead of a bare z.string().

Feed it an array of same-shaped objects, such as rows from a list endpoint, and the generator merges every element before typing a field: a key missing from some rows becomes .optional(), a key that is sometimes null becomes .nullable() or .optional() depending on the setting you pick, and a field that repeats a small set of values — a status or role column — becomes a proper z.enum() instead of an open-ended string. Turn on "Split nested objects" to pull every nested object into its own named export instead of one deeply nested literal, with identical shapes automatically sharing a single schema. Turn on "Strict objects" to reject any key the sample never showed, catching typos and unexpected API fields at parse time instead of downstream.

Name the exported schema however your codebase expects, and optionally add export type X = z.infer<typeof X> right below it, so the runtime validator and the compile-time type come from the same source and can never quietly drift apart.

Everything runs locally in your browser. The JSON you paste — API responses carrying tokens, user records, internal payloads — is parsed and typed entirely on your device and never uploaded anywhere. Paste a sample, copy the generated schema into your project, and start validating what you parse.

FAQ

How does it decide between .optional() and .nullable()?
A key missing from some samples in an array is always .optional() — there's no other way to allow a key not existing. A key that's present but sometimes null is ambiguous, so the "Null handling" option decides: nullable types it accurately with .nullable(), optional treats a null value the same as a missing one.
How are nested objects handled?
By default a nested object is typed inline, right where it appears. Turn on "Split nested objects" to give each nested shape its own export const ...Schema, and identical shapes automatically share one schema instead of duplicating it.
When does a field become z.enum() instead of z.string()?
When "Detect enums" is on and a field repeats a small set of distinct string values — 2 to 5 — across an array of samples, such as a status or category column. A field with only unique values, or too many distinct ones, stays z.string().
What does "Strict objects" do?
It appends .strict() to every generated z.object(), so parsing fails if the input carries a key the sample never showed — useful for catching typos or unannounced API additions instead of silently ignoring them.
Is my JSON uploaded anywhere?
No. The schema is generated entirely in your browser — your JSON never leaves your device.