Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the zod to json schema engine, best practices for implementation, and data security standards.
Zod is a TypeScript-runtime validator — its schemas live in your code and can't be read by tools that speak the cross-language standard, JSON Schema. Converting Zod to JSON Schema unlocks the rest of the ecosystem: OpenAPI documents, form generators, contract tests, validators in other languages, and LLM structured outputs. This converter turns a Zod schema into a JSON Schema document you can hand to any of them. It's a faithful, mechanical translation you can read line by line — a starting point you keep refining, not a black box.
// Input: Zod schema
import { z } from 'zod';
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().min(0).max(120),
role: z.enum(['admin', 'user', 'guest']),
bio: z.string().nullable(),
tags: z.array(z.string()).max(10),
});
// Output: JSON Schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 120 },
"role": { "type": "string", "enum": ["admin", "user", "guest"] },
"bio": { "type": ["string", "null"] },
"tags": { "type": "array", "items": { "type": "string" }, "maxItems": 10 }
},
"required": ["id", "email", "age", "role", "bio", "tags"]
}
There are three common routes, and it's worth knowing which fits:
z.toJSONSchema(schema) ships in the core library — the right call if you're already on v4 and want it in your own runtime.$ref strategy.For a permanent, in-code pipeline, prefer the built-in or the npm library. For exploration, drafting, and privacy-sensitive payloads, do it here.
Unlike a TypeScript type, JSON Schema can express runtime constraints, so far more of the Zod schema survives:
z.string().min(3).max(20) → "minLength": 3, "maxLength": 20z.string().email() / .uuid() / .url() → "format": "email" | "uuid" | "uri"z.string().regex(re) → "pattern": "..."z.number().int() → "type": "integer"; .min(0) → "minimum": 0z.array(T).min(1).max(10) → "minItems": 1, "maxItems": 10z.enum([...]) → "enum": [...]; z.literal(x) → "const": x.optional() → omitted from required; .nullable() → "type": ["T", "null"].describe('...') → "description": "..."; .default(v) → "default": vReused sub-schemas are emitted once under $defs and referenced, keeping the document DRY and matching how OpenAPI's components/schemas works:
const AddressSchema = z.object({ city: z.string(), zip: z.string() });
const OrderSchema = z.object({
billing: AddressSchema,
shipping: AddressSchema,
});
// →
{
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/Address" },
"shipping": { "$ref": "#/$defs/Address" }
},
"required": ["billing", "shipping"],
"$defs": {
"Address": {
"type": "object",
"properties": { "city": { "type": "string" }, "zip": { "type": "string" } },
"required": ["city", "zip"]
}
}
}
z.union([z.string(), z.number()])
// → { "anyOf": [ { "type": "string" }, { "type": "number" } ] }
z.discriminatedUnion('type', [A, B])
// → { "oneOf": [ ...A, ...B ] } (the discriminator stays a const on each branch)
z.string().nullable()
// → { "type": ["string", "null"] }
OpenAI and Anthropic tool/function calling expect a JSON Schema for the arguments. If you already validate with Zod, convert it instead of hand-writing the schema twice:
const ExtractSchema = z.object({
sentiment: z.enum(['positive', 'neutral', 'negative']),
score: z.number().min(0).max(1),
topics: z.array(z.string()),
});
// Feed the converted JSON Schema as the tool's parameters,
// then validate the model's response back through ExtractSchema.
// One Zod schema drives both the request contract and the runtime check.
Note: providers in "strict" mode reject some keywords (e.g. they may require additionalProperties: false and disallow certain formats). Treat the output as a draft and trim it to the provider's accepted subset.
JSON Schema has incompatible dialects. Draft-07 is the most widely supported by older tooling. 2020-12 aligns with OpenAPI 3.1 and is the modern default. The biggest practical differences: $defs (2020-12) vs definitions (draft-07), and how nullable and tuples are expressed. Pick the draft your consumer expects — emitting 2020-12 into a draft-07 validator is a common source of silent mismatches.
.transform() / .pipe(): JSON Schema validates, it doesn't transform. Transforms are dropped — keep them in Zod..refine() logic: arbitrary predicates have no declarative equivalent; they can't be represented and are omitted.Because of these, a converted schema validates the shape and declarative constraints, not your transform/refine logic. Zod remains the source of truth for anything procedural.
Q: draft-07 or 2020-12?
A: Match the consumer. Use 2020-12 for OpenAPI 3.1 and modern validators; draft-07 for older tooling that doesn't understand $defs or 2020-12 keywords.
Q: Why is a .refine() rule missing from the output?
A: Custom refinements are arbitrary code with no JSON Schema equivalent, so they're dropped. Declarative rules (min, max, regex, email) do convert. Keep procedural checks in Zod.
Q: Can I use this for OpenAPI?
A: Yes — OpenAPI 3.1's schema objects are JSON Schema 2020-12. Drop the converted schema into components/schemas. (TypeMorph also has a direct Zod → OpenAPI path.)
Q: How are optional vs nullable handled?
A: .optional() removes the key from required; .nullable() adds "null" to the type. .nullish() does both.
Q: Is my schema uploaded anywhere?
A: No. The conversion runs entirely in your browser — nothing is sent to a server.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.
Why pasting proprietary company data into third-party web tools is a major liability, and how to stay safe.
A deep dive into combining Zod, React Query, and TypeScript for bulletproof API integration.
Code generation is just the beginning. Discover how a schema-first approach can eliminate 90% of your integration bugs.