JSON to Zod Schema Converter — Generate Zod Types

TypeScript teams using Zod reach for this page when a schema has to exist before the code that depends on it does. The usual moment is an API response sample landing in a ticket or a network tab with no documentation attached. Paste that JSON into the left pane and the converter walks the structure, printing a Zod schema and its inferred type in the right pane. One schema then covers two jobs: it validates the payload at runtime and it supplies the TypeScript type used across the project. Hand-written interfaces drift as the server changes, while a schema regenerated from a fresh sample stays tied to what the API really sends.

Preset examples
Conversion runs in this tab as you type.
Nested object, string array, an ISO timestamp detected as datetime, and a float score.
—

How the Converter Walks Your JSON

Everything starts with a parse, so malformed input stops at the red banner with the parser message instead of producing broken code. A recursive walk then visits each value and records two figures: the number of nodes seen and the deepest nesting reached, both shown in the stats line between the panes. Objects come out as z.object calls with one property per line, keys that are valid identifiers are printed bare, and keys containing hyphens, spaces, or other punctuation are quoted so the schema still compiles. The walk finishes by wrapping the root in an import line, an exported const, and an exported type, which is the module shape most projects want to commit.

Primitives map to a single call each. Strings start as z.string(), and with detect dates on, an ISO 8601 timestamp becomes z.string().datetime() instead. Whole numbers become z.number().int(), decimals become z.number(), and coerce mode swaps both for z.coerce.number(); booleans map to z.boolean() and null maps to z.null(). Arrays collect the element schema: an empty array falls back to z.array(z.unknown()), a uniform array prints one element type, and a mixed array becomes a union of array variants so every observed shape stays valid.

What the options change

The name field decides the exported symbols: entering User gives UserSchema along with type User. Characters that cannot appear in an identifier are stripped before the name is printed, and a name starting with a digit gets a T in front. Datetime detection starts on because API timestamps are the most common string worth checking, while coerce mode starts off so integers keep the int check. The format button re-prints minified input with two-space indentation.

Reading the stats line and the error banner

The stats line updates after every render and reports node count and depth; a sudden jump usually means a whole list was pasted where a single record was expected. The banner appears only when the conversion cannot run, which covers an empty input and JSON that fails to parse. While the banner shows, the output pane keeps the last good schema, so a half-typed quote does not wipe the code you were about to copy.

How It Works

  1. Load a JSON sample

    Paste a response into the input pane, or press one of the four preset buttons to load a profile object, a paginated list, tricky keys, or a root array.

  2. Set the name and options

    Type the exported name you want, keep detect dates on for ISO timestamps, and turn on coerce numbers when the payload carries numeric text.

  3. Read the generated code

    The output pane prints the schema and the inferred type as you type, and the stats line reports node count and nesting depth.

  4. Copy the module into your project

    Press Copy to send the generated code to the clipboard, then paste it beside the code that calls the API and import the exported type.

Before and after: a response turned into a schema

Before (a sample response)
{
  "id": 1042,
  "email": "ada@example.com",
  "createdAt": "2026-01-15T09:30:00Z",
  "score": 98.5,
  "roles": ["admin", "billing"],
  "verified": true
}
After (detect dates on, name set to User)
import { z } from 'zod';

export const UserSchema = z.object({
  id: z.number().int(),
  email: z.string(),
  createdAt: z.string().datetime(),
  score: z.number(),
  roles: z.array(z.string()),
  verified: z.boolean(),
});

export type User = z.infer<typeof UserSchema>;

Use Cases

Developer Production Workflow
Typing a fetch response so field access compiles against the real shape Validating webhook payloads at the edge before a handler runs Onboarding a third-party API by pasting a sample response from its docs
Replacing a hand-written interface that no longer matches the endpoint Rejecting malformed config JSON at process start with a clear path in the log Reviewing API changes by diffing the schema generated from a new sample
Parsing stored records through one schema instead of scattered casts Failing fast at the consumer when a queue message arrives malformed Sharing an agreed payload shape by committing the copied schema to the repo

Frequently Asked Questions

What is Zod and what does this page produce?

Zod is a TypeScript library where data shapes are declared as schema objects that execute at runtime. Calling a schema’s parse method with unknown input either returns the typed value or throws on the first mismatch, and z.infer turns the schema into a matching TypeScript type. This page reads your JSON sample and prints both the schema and the inferred type, ready to paste into a project that already depends on the zod package. The export is a normal module: one import line, an exported const, and an exported type.

Why is a schema generated from a sample useful?

A hand-written interface records what someone believed the endpoint returned when the file was last edited, and it goes stale as soon as the server renames a field. A schema generated from a fresh response reflects the keys, nesting depth, and value kinds that are really present, including fields nobody documented. Because the schema also runs at runtime, a missing key or a string where a number belongs fails at the boundary where you can log it. Teams often regenerate after an API version bump and diff the two outputs to see exactly what moved.

How does it handle empty arrays, nulls, and mixed values?

An empty array gives the walk nothing to sample, so it becomes z.array(z.unknown()) and accepts any list until you name the element type yourself. A null becomes z.null(), which rejects any other value at that position. When one array holds more than one kind of item, the output is a union of array schemas with a branch for each distinct element type seen in the sample. An empty object likewise becomes an object schema with no keys, which you can extend after copying.

How does datetime detection work?

With the detect dates box checked, each string is matched against an ISO 8601 pattern: a date, the letter T, a time, and a zone such as Z or +05:30. A hit turns z.string() into z.string().datetime(), so a malformed timestamp is rejected before it reaches a Date constructor. Strings that only look related, such as a version number or a plain date with no time, keep the ordinary z.string() form. Clear the checkbox when your payload carries identifiers that happen to start with digits.

What changes when coerce mode is on?

Without coerce mode, a whole number becomes z.number().int() and a decimal becomes z.number(), so the number 42 passes while the text 42 is rejected. With coerce mode on, every numeric field is emitted as z.coerce.number() and the integer check is dropped, which suits values that were stringified in transit such as query parameters, CSV cells, or an older endpoint that prints numbers as text. Zod converts the value before validating, so your code still receives a number after parse. Leave the box off for a normal JSON API, where the payload already carries real numbers.

Can I change the generated code afterwards?

Yes, the output is ordinary TypeScript and is meant to be edited. Typical adjustments are adding a minimum length to a string, marking a field optional when the server sometimes omits it, or replacing a union with a single element type once the payload stabilizes. Because the inferred type is derived from the schema, your edits reach the type on the next compile and the editor flags every call site that disagrees.

Does any of my JSON leave the browser?

No. Parsing, the tree walk, and the option toggles all run inside the page, and the conversion makes no network request carrying your payload. Nothing is written to storage or logs, so samples containing customer records or internal identifiers can be pasted here. The page only loads its own scripts and fonts, and closing the tab discards the input, the output, and the preset note.