Skip to content

JSON to Zod Schema

Convert a JSON sample into Zod schemas with their inferred TypeScript types, nested objects and arrays named and deduplicated, live in your browser.

Generated files

Generated files appear here.

Diagnostics appear here after you paste your input.

Processed locally in your browser. Your data never leaves your device.

About this JSON to Zod schema converter

Paste a JSON sample and get Zod schemas: one per object shape, nested objects and arrays handled recursively, declared in dependency order so each schema can reference the ones it needs. This page is the Zod output of the JSON to Types studio; TypeScript-only interfaces and PHP DTOs are one click away and use the same inference.

A worked example

This JSON:

{
  "id": 1,
  "name": "Ada",
  "tags": ["admin", "editor"],
  "nickname": null
}

becomes:

import { z } from 'zod'

export const RootSchema = z.object({
    id: z.number(),
    name: z.string(),
    tags: z.array(z.string()),
    nickname: z.null(),
})
export type Root = z.infer<typeof RootSchema>

How to use it

  1. Paste JSON, drop a file, or load an example.
  2. Turn Strict mode on if unknown keys should fail validation.
  3. Copy or download the file, and run `npm install zod` in your project if you have not already.

Frequently asked questions

Do I need to install anything to use the output?
The generated file imports from the zod package (`npm install zod`); this page does not depend on it and works without it. Any Zod v3 or v4 accepts the generated syntax.
Why generate Zod instead of plain TypeScript?
A Zod schema validates data at runtime (parsing an API response, a form submission, an environment variable) and, by default, also exports the equivalent TypeScript type next to it (`z.infer<typeof XSchema>`), so one file often replaces both a validator and a type declaration.
What does Strict mode do?
Adds .strict() to every object schema, so parsing fails if the input has a key the schema does not know about. Off by default (Zod's own default silently strips unknown keys), which matches typical API responses that may gain fields over time.
How are optional and nullable fields written?
A key that is sometimes missing gets .optional(); a value that is sometimes null gets .nullable(); a field that is both gets both, in that order: `z.string().nullable().optional()`.
What happens to a field with more than one type?
It becomes z.union([...]) of the types actually observed (for example z.union([z.number(), z.string()])), never a guess at one of them.