Hunt
Skip to content

JSON Schema to Zod

Paste a JSON Schema, from an API, an OpenAPI component or a form definition, to get equivalent Zod 4 code with TypeScript types. Definitions become named schemas, recursive references use Zod 4's getter pattern, and anything Zod can't express is listed instead of silently dropped. Nothing is uploaded.

Runs in your browser

JSON Schema

Paste a schema or press Sample

Zod

Zod code will appear here

What is converted

  • Types and formats: string, number, integer (z.int()), boolean, null, and formats such as email, uri, uuid, date-time and ipv4 as z.email(), z.url() and so on.
  • Limits: minLength, maxLength, pattern, minimum, maximum, exclusive limits, multipleOf, minItems and maxItems.
  • Objects: required (other properties are .optional()), default (.default()), additionalProperties: false (z.strictObject) or a schema (.catchall()), and maps (z.record).
  • Combinations: enum, const, anyOf and allOf (as .extend() for the common base-plus-properties pattern); oneOf with a shared tag property becomes z.discriminatedUnion.
  • References: $defs and definitions become named schemas, declared before use; recursive ones use getters, which Zod 4 needs for recursive types.
  • OpenAPI 3.0: nullable: true becomes .nullable().

How to use the JSON Schema to Zod

  1. Paste a JSON Schema, or press Sample.
  2. Set the root name (the schema's title is used by default).
  3. Read the notes for anything that wasn't converted.
  4. Copy the code or download it as a .ts file. It needs zod version 4.

Examples

An object with formats, an enum and a nested object

Input
{"title":"User","type":"object","properties":{"id":{"type":"integer","minimum":1},"email":{"type":"string","format":"email"},"role":{"enum":["admin","member"]},"address":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}},"required":["id","email"]}
Output
import { z } from "zod";

export const UserSchema = z.object({
id: z.int().min(1),
email: z.email(),
role: z.enum(["admin", "member"]).optional(),
address: z.object({
city: z.string(),
}).optional(),
});
export type User = z.infer<typeof UserSchema>;

A recursive definition

Input
{"title":"Tree","$ref":"#/$defs/node","$defs":{"node":{"type":"object","properties":{"name":{"type":"string"},"children":{"type":"array","items":{"$ref":"#/$defs/node"}}},"required":["name"]}}}
Output
import { z } from "zod";

export const NodeSchema = z.object({
name: z.string(),
get children() {
return z.array(NodeSchema).optional();
},
});
export type Node = z.infer<typeof NodeSchema>;

export const TreeSchema = NodeSchema;
export type Tree = z.infer<typeof TreeSchema>;

The getter lets NodeSchema refer to itself; Zod 4 infers the recursive type from it.

allOf: a base schema extended

Input
{"title":"Admin","$defs":{"base":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},"allOf":[{"$ref":"#/$defs/base"},{"properties":{"level":{"type":"integer","maximum":5}},"required":["level"]}]}
Output
import { z } from "zod";

export const BaseSchema = z.object({
id: z.string(),
});
export type Base = z.infer<typeof BaseSchema>;

export const AdminSchema = BaseSchema.extend({
level: z.int().max(5),
});
export type Admin = z.infer<typeof AdminSchema>;

Why a new converter

The popular json-schema-to-zod package stopped being maintained in March 2026 and targets Zod 3. This converter writes Zod 4 code: z.email() instead of z.string().email(), z.int(), z.strictObject and getters for recursion.

Options

Root name
The root schema is exported as <name>Schema; by default the schema's title, or Root.
Export types
Export a TypeScript type for each schema with z.infer (on by default).
Descriptions
Keep descriptions as .describe(), which also appears in JSON Schema generated back from Zod.

Limitations

  • Not converted (listed in the notes): uniqueItems, patternProperties, propertyNames, not, if/then/else, contains, dependentRequired and the unevaluated keywords.
  • $refs to other files or URLs aren't fetched.
  • oneOf without a tag property becomes z.union(), which doesn't check that only one option matches.

Frequently asked questions

Is my schema uploaded?

No. The conversion runs in your browser; nothing you paste is sent anywhere.

Does it work with Zod 3?

The output uses Zod 4 APIs (z.email(), z.int(), z.strictObject, getters). Upgrade to Zod 4, or replace those with their Zod 3 forms such as z.string().email().

Can I convert OpenAPI schemas?

Yes: paste a schema from components.schemas. nullable: true from OpenAPI 3.0 is understood. Copy referenced schemas into $defs and point the $refs at them.

How do I go from sample JSON to Zod?

Use JSON to Zod Schema, which infers the schema from example data.