mrkeyoor.com_
Thu 06 Aug 01:04 UTC
npmUtilsupdated 05 Aug 2026

zod-to-json-schema

This package walks a Zod 3 schema and emits the equivalent JSON Schema document, so a validator you already wrote in TypeScript can also describe an OpenAPI request body, an LLM tool definition, or a form config. It handles nested objects, unions, recursion through internal $refs, string and number constraints, and can target draft-07, 2019-09, OpenAPI 3.0, or OpenAI structured output mode. It matters far beyond its 1.2k stars because it sits underneath LangChain, MCP servers, and other agent tooling that needs a JSON Schema for every tool. Read the next section before adding it: the author deprecated it in November 2025 and the repository is archived.

Verdict

Do not add this to a new project: Zod 4 does the same job in core and this repository is archived. It stays worth knowing because millions of installs a week arrive through agent and OpenAPI tooling that has not migrated yet, and configuring it correctly is often the difference between a tool schema an LLM provider accepts and one it rejects.

API stability3/5One function with an options object, and the author kept breaking changes rare in practice. But the package explicitly rejects semantic versioning, so features have landed in patch releases and a lockfile refresh could change your output.
Docs4/5A long README documenting every option with input and output examples, a candid known-issues list covering transforms, record key types, and relative pointers, plus a changelog. It is one flat page with no site or searchable reference.
Maintenance1/5Archived on GitHub and formally deprecated by the author in November 2025, with the last release in March 2026. Issues cannot be filed, pull requests cannot be merged, and there is no successor maintainer.
Ecosystem4/5Roughly 60M weekly downloads because agent frameworks and OpenAPI generators pull it in transitively, so integration examples are everywhere. That reach is a snapshot of the past, not momentum: the packages depending on it are steadily moving to Zod 4 built-ins.

Use it if

  • You are stuck on Zod 3 schemas, in your own code or through a dependency, and still need JSON Schema out of them; this is the only mature converter for that shape
  • You already depend on it transitively through LangChain, an MCP server SDK, or a similar package and want to understand and configure what it produces
  • You need OpenAPI 3.0 output specifically, where nullable and the missing 3.1 alignment mean plain draft-07 will not do
  • You need output shaped for OpenAI structured outputs, where optional properties have to become required and nullable
Skip it if

Setup reality

npm install zod-to-json-schema, import zodToJsonSchema, call it. There is no config file and zod is a peer dependency accepting ^3.25.28 or ^4, so npm and pnpm will not add it for you. The trap is that peer range: version 3.25 accepts Zod 4 as a peer but does not accept Zod 4 schemas, so on a Zod 4 project you have to import { z } from 'zod/v3' and keep writing v3 schemas for anything you pass in. Handing it a v4 schema fails at runtime rather than at compile time. Beyond that, the defaults surprise people: output is draft-07, a name string as the second argument wraps everything in a definitions block behind a $ref, and objects get additionalProperties: false by default, which some consumers (Google's Gen AI API, for one) reject outright until you set allowedAdditionalProperties and rejectedAdditionalProperties to undefined.

Patterns

Convert a schema to JSON Schemabasic-convert

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const User = z.object({
  name: z.string().min(2),
  age: z.number().int().optional(),
});

const schema = zodToJsonSchema(User);
// { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', ... }

Default target is draft-07 and objects come out with additionalProperties: false, mirroring how Zod strips unknown keys when parsing.

Name the schema and put it in definitionsnamed-definitions

const schema = zodToJsonSchema(User, 'User');
// { $ref: '#/definitions/User', definitions: { User: { ... } } }

// or with the options object
const schema2 = zodToJsonSchema(User, {
  name: 'User',
  definitionPath: '$defs',
});

Passing a bare string as the second argument is the same as { name }. Consumers that expect a plain object at the root will choke on the $ref wrapper, so only name it when the caller understands definitions.

Target OpenAPI 3.0openapi-3-target

const schema = zodToJsonSchema(User, {
  target: 'openApi3',
  $refStrategy: 'none',
});

OpenAPI 3.0 predates JSON Schema draft-07 compatibility, so nullable and a few keywords differ. OpenAPI 3.1 is regular JSON Schema, so leave the default target there.

Produce a schema for OpenAI structured outputopenai-tool-schema

const Args = z.object({
  city: z.string(),
  units: z.enum(['c', 'f']).optional(),
});

const parameters = zodToJsonSchema(Args, {
  target: 'openAi',
  $refStrategy: 'none',
});

const tool = {
  type: 'function',
  function: { name: 'get_weather', parameters },
};

The openAi target rewrites optional properties as required-but-nullable, which is what strict mode demands. The README calls this target experimental, so some option combinations still produce schemas the API rejects.

Hoist repeated sub-schemasshared-definitions

const Address = z.object({ street: z.string(), zip: z.string() });

const Order = z.object({ billing: Address, shipping: Address });

const schema = zodToJsonSchema(Order, {
  definitions: { Address },
});
// billing and shipping both become { $ref: '#/definitions/Address' }

Without this, the same object literal is inlined at every use site. It works alongside a schema name and a custom definitionPath.

Inline everything instead of using $refno-refs

const schema = zodToJsonSchema(Order, { $refStrategy: 'none' });

Many LLM providers and form builders do not resolve $ref. 'none' inlines repeated schemas, but a genuinely recursive schema degrades to {} because it cannot be expanded, so check recursive types after switching.

Drop the additionalProperties keyword entirelyadditional-properties

const schema = zodToJsonSchema(User, {
  allowedAdditionalProperties: undefined,
  rejectedAdditionalProperties: undefined,
});

Some APIs reject the keyword outright rather than reading its value. Both options are ignored if your object uses .catchall(), because the catchall schema is emitted instead.

Carry Zod error messages into the outputerror-messages

const Email = z.string().email('Invalid email').min(5, 'Too short');

const schema = zodToJsonSchema(Email, { errorMessages: true });
// { type: 'string', format: 'email', minLength: 5,
//   errorMessage: { format: 'Invalid email', minLength: 'Too short' } }

The errorMessage keyword is an ajv-errors extension, not standard JSON Schema. Plain ajv ignores it until you install and enable ajv-errors.

Attach title and examples through the descriptionadd-metadata

import { zodToJsonSchema, jsonDescription } from 'zod-to-json-schema';

const schema = zodToJsonSchema(
  z.string().describe(
    JSON.stringify({ title: 'City', examples: ['Ahmedabad', 'Pune'] }),
  ),
  { postProcess: jsonDescription },
);

Zod 3 has no metadata API, so this JSON-in-the-description trick is the supported workaround. Zod 4's .meta() removes the need for it, which is one more reason the package was retired.

Override or remove a specific propertyoverride-output

import { zodToJsonSchema, ignoreOverride } from 'zod-to-json-schema';

const schema = zodToJsonSchema(User, {
  override: (def, refs) => {
    if (refs.currentPath.join('/') === '#/properties/age') {
      return { type: 'integer', minimum: 0 };
    }
    return ignoreOverride;
  },
});

You must return the ignoreOverride symbol to leave a node alone. Returning undefined deletes the property from the output, which is a very easy accident to ship.

Keep using it on a Zod 4 projectzod-v3-under-zod-4

import { z } from 'zod/v3';
import { zodToJsonSchema } from 'zod-to-json-schema';

const Legacy = z.object({ id: z.string().uuid() });
const schema = zodToJsonSchema(Legacy);

Version 3.25 accepts Zod 4 as a peer dependency but only converts v3 schemas. Passing a schema built from the v4 import fails at runtime, not at type-check time.

Replace it with the Zod 4 built-inmigrate-to-zod-4

import { z } from 'zod';

const User = z.object({
  name: z.string().min(2),
  age: z.number().int().optional(),
});

const schema = z.toJSONSchema(User, { target: 'draft-7' });

Output is not byte-identical to this package: default target, $ref placement, and how unrepresentable types are handled all differ, so diff a real schema against your consumer before swapping.

Alternatives

PackageRegistryPick it when
zodnpmYou are on or can move to Zod 4, where z.toJSONSchema() replaces this package and is maintained alongside the validator
@sinclair/typeboxnpmYou would rather write JSON Schema directly and derive TypeScript types from it, so no conversion step exists to go wrong
@valibot/to-json-schemanpmYou are picking a validator now and want a smaller, tree-shakeable one with an officially maintained JSON Schema converter