Generate a Zod schema from JSON
Zod gives you runtime validation and a static TypeScript type from one schema definition, but writing the schema to match a JSON payload by hand is slow and error-prone — especially for deeply nested API responses. This tool infers a ready-to-use Zod schema plus its inferred TypeScript type from a representative JSON sample, so you can jump straight from a raw API payload to working validation in seconds.
How it works
The generator does a recursive walk of your JSON sample and maps every value to its corresponding Zod validator:
| JSON value | Generated Zod type |
|---|---|
"hello" | z.string() |
42 | z.number() |
true / false | z.boolean() |
null | z.null() |
{ key: value } | z.object({ key: <T> }) |
["a", "b"] | z.array(z.string()) |
[1, "x"] (mixed) | z.array(z.union([z.number(), z.string()])) |
[] (empty) | z.array(z.unknown()) |
Nested objects are handled recursively, so even a deeply structured payload
like { user: { address: { city: "London" } } } produces properly nested
z.object() calls. The output also exports a matching TypeScript type via
z.infer<typeof schema>, which means you get both runtime validation and a
static type from a single definition — exactly what makes Zod worth using.
Worked example
Take this API response representing a blog post:
{
"id": 7,
"title": "Hello world",
"published": true,
"author": { "name": "Sam", "id": 42 },
"tags": ["typescript", "zod"]
}
The generator produces:
import { z } from "zod";
const schema = z.object({
id: z.number(),
title: z.string(),
published: z.boolean(),
author: z.object({
name: z.string(),
id: z.number(),
}),
tags: z.array(z.string()),
});
type Schema = z.infer<typeof schema>;
You can then pass any unknown API response through schema.parse(data) and get
a fully typed object — or use schema.safeParse(data) to get a result object
without throwing on invalid input.
Practical guidance
What to paste as your sample. Use a real, representative response from your
API, not a minimal stub. The more fields you include in the sample, the more
complete your generated schema. If your payload sometimes omits a field, the
schema will assume it is always present — add .optional() to those fields
after generating.
Handling nullable vs optional fields. If a field can be null at runtime,
the inferred z.string() will reject it. Either chain .nullable() on the
field (for string | null) or .nullish() (for string | null | undefined).
Arrays with a single sample element. The array type is inferred from all
the elements in your sample array. An array with one number element generates
z.array(z.number()) — if the real array can also contain strings, add them
to the sample before generating.
When to use this over writing schemas by hand. Manual schemas give you
more control over refinements (.min(), .email(), .regex()) and
discriminated unions — but they take time. Use the generator to get the
structural scaffolding in place, then add refinements on top.
All inference runs entirely in your browser — your JSON is never uploaded or stored.