>_devtools

JSON Schema to Zod

Convert a JSON Schema into a Zod 4 or Zod 3 validator, with named $defs, recursion via z.lazy, and inferred types.

What this does

Generates Zod validators from a JSON Schema, so the same contract can be enforced at runtime in TypeScript code. Each entry in $defs or definitions becomes a named constant, emitted before the schemas that use it, followed by the root schema and (optionally) the inferred TypeScript types.

import { z } from "zod";

export const Address = z.object({
  city: z.string(),
});

export type Address = z.infer<typeof Address>;

export const User = z.object({
  id: z.number().int().min(1),
  email: z.email(),
  address: Address.optional(),
});

export type User = z.infer<typeof User>;

Keyword mapping

  • Strings: minLength, maxLength, and pattern become .min(), .max(), and .regex(); the formats email, uuid, uri, date-time, date, time, ipv4, and ipv6 become Zod's string formats.
  • Numbers: integer, minimum, maximum, exclusiveMinimum, exclusiveMaximum, and multipleOf.
  • Arrays: minItems, maxItems, and tuples from prefixItems.
  • Objects: properties not in required are .optional(); additionalProperties: false becomes .strict(), a schema there becomes .catchall().
  • A type list that includes null becomes .nullable(); other lists become z.union(). anyOf and oneOf become unions, allOf an intersection, enum a z.enum() or literal union.
  • description becomes .describe().

Versions and recursion

Choose Zod 4 (the default) or Zod 3. They differ mainly in string formats: Zod 4 uses top-level helpers such as z.email(), while Zod 3 uses z.string().email(). Recursive definitions are emitted with z.lazy() and an explicit TypeScript type, which TypeScript needs because it cannot infer a type that refers to itself.

What is not expressed

The tool reports a warning under the output for keywords Zod cannot express directly:uniqueItems, not, if/then/else,patternProperties (accepted as a record without checking keys), and the exactly-one-match rule of oneOf. References to other files or URLs are never fetched and becomez.unknown(). Input is limited to 1 MB.

Privacy

The conversion runs in your browser. Your schema is never uploaded or stored.