What this tool does
TypeScript types disappear when your code is compiled, so they can't catch an API that returns something unexpected. A Zod schema checks the data at runtime, and z.infer gives you the TypeScript type from the same source, so the two can't drift apart.
Writing the schema for a large response by hand is tedious. This tool reads a sample and writes a Zod 4 schema for it, in your browser: paste real responses without anything being uploaded.
How to use the JSON to Zod Schema
- Paste a JSON sample into the left panel, or press Sample to try an example.
- The Zod schema appears on the right as you type.
- Set the root name, choose how unknown keys are handled, and turn format detection and exported types on or off.
- Copy the result or download it as
schema.ts, then install Zod (npm install zod) in your project.
Examples
Generate a schema and its type
{"id":7,"name":"Ada","active":true}import { z } from "zod";
export const RootSchema = z.object({
id: z.number(),
name: z.string(),
active: z.boolean(),
});
export type Root = z.infer<typeof RootSchema>;
Optional and nullable fields
{"orders":[{"id":"o1","coupon":"SAVE10","note":null},{"id":"o2","note":"gift"}]}import { z } from "zod";
export const OrderSchema = z.object({
id: z.string(),
coupon: z.string().optional(),
note: z.string().nullable(),
});
export type Order = z.infer<typeof OrderSchema>;
export const RootSchema = z.object({
orders: z.array(OrderSchema),
});
export type Root = z.infer<typeof RootSchema>;
coupon is missing from the second order, so it is optional. note is present in both but null in one, so it is nullable.
Formats are detected
{"id":"123e4567-e89b-12d3-a456-426614174000","email":"ada@example.com","website":"https://jshunt.dev","createdAt":"2026-11-08T09:41:22Z"}import { z } from "zod";
export const UserSchema = z.object({
id: z.uuid(),
email: z.email(),
website: z.url(),
createdAt: z.iso.datetime({ offset: true }),
});
export type User = z.infer<typeof UserSchema>;
Reject unknown keys, without types
{"name":"Ada","tags":["math"]}import { z } from "zod";
export const RootSchema = z.strictObject({
name: z.string(),
tags: z.array(z.string()),
});
How the schema is built
The same rules as the JSON to TypeScript generator, written as Zod:
- Objects become named schemas: a
customerobject becomesCustomerSchema, and arrays of objects use the singular (orders→OrderSchema). Child schemas are declared before the schemas that use them, as Zod requires. - Optional fields: a key missing from some items of an array gets
.optional(). A value that is sometimesnullgets.nullable(). - Unions: values of different types become
z.union([…]). - Formats: with Detect formats on, a string field becomes
z.email(),z.url(),z.uuid(),z.iso.datetime({ offset: true })orz.iso.date()when every value in the sample matches that format. - Empty values: an empty array becomes
z.array(z.unknown())and an empty objectz.record(z.string(), z.unknown()). - Types: each schema gets a matching
export type … = z.infer<typeof …>, unless you turn Export types off.
Tighten the schema before you rely on it
A schema generated from one sample accepts that sample, but it can't know your rules. Review it and add what only you know:
- Fixed sets of values: replace
z.string()withz.enum(["draft", "published"]). - Number rules: add
.int(),.positive()or.min()where they apply. - Fields that are always
nullin the sample getz.null(); widen them, for example toz.string().nullable(). - Unknown keys: choose whether objects strip, reject or keep keys the schema doesn't list (see Options).
Options
- Root name
- Names the top-level schema and type:
UsergivesUserSchemaandUser. - Unknown keys
- What object schemas do with keys they don't list. Strip (
z.object, the default) removes them from the parsed result; Reject (z.strictObject) fails validation; Keep (z.looseObject) passes them through. - Detect formats
- Uses Zod's format schemas for strings when every sample value matches: email, URL (http or https), UUID, ISO date-time and ISO date. Turn it off to get
z.string()everywhere. - Export types
- Adds
export type Name = z.infer<typeof NameSchema>after each schema.
Limitations
- The schema describes one sample. It can't know enums, number ranges, or fields the sample doesn't include.
- The output uses Zod 4 syntax (
z.email(),z.iso.datetime(),z.strictObject). Zod 3 projects need small changes, such asz.string().email(). - Strict JSON only. To fix invalid JSON first, use the JSON Fixer.
- Inputs up to 10 MB are supported. Generation runs in your browser.
Frequently asked questions
Is my JSON sent to a server?
No. The schema is generated entirely in your browser. Nothing you paste is uploaded, stored or logged.
Why use Zod instead of TypeScript types?
TypeScript checks your code when it compiles; it can't check data that arrives at runtime from an API, a file or a form. Zod validates that data, and z.infer derives the TypeScript type from the schema, so you only maintain one definition.
Does it work with Zod 3?
The output targets Zod 4. For Zod 3, replace the format helpers with their older forms (z.string().email(), z.string().url(), z.string().uuid(), z.string().datetime({ offset: true }), z.string().date()), and z.strictObject({…}) with z.object({…}).strict().
What is the difference between optional and nullable?
.optional() means the key may be missing; .nullable() means the key is present but its value may be null. The tool chooses based on what your sample shows, and uses both only if a field is sometimes missing and sometimes null.
How do I validate data with the generated schema?
Call RootSchema.parse(data) to get typed data or an exception, or RootSchema.safeParse(data) to get a result object you can check without try/catch.