zod-to-json-schema

repository·master·Indexed 22 days ago

https://github.com/stefanterdell/zod-to-json-schema

A utility library that converts Zod schemas into JSON schemas, supporting recursive schemas, OpenAPI 3.0, and OpenAI strict mode. It provides the zodToJsonSchema function for conversion and an options object for fine-grained control over targeting, reference handling, and data strategies. Key features include custom error message inclusion, an override callback for specific type parsing, and a postProcess callback for final schema transformation. Note: As of November 2025, this project is no longer actively maintained; users are recommended to switch to Zod v4.

Tokens
4K
Snippets
9
Records
17
Agent score
78%

What's inside zod-to-json-schema

  1. Convert Zod schemas to JSON Schema

    master

    The zod-to-json-schema library converts Zod schemas into JSON schemas. It supports basic validations (string, number, array length), string patterns, and resolves recursive/recurring schemas using internal $refs.

    Key features include:

    • Support for legacy OpenAPI 3.0 specification.
    • Support for OpenAI strict mode (replaces optional properties with required but nullable ones).
    • Compatibility with Zod v4 as a peer-dependency (as of v3.25, provided you use v3-schemas).
    import { z } from "zod"; // Or, using v3.25 or v4, "zod/v3"
    import { zodToJsonSchema } from "zod-to-json-schema";
    
    const mySchema = z
      .object({
        myString: z.string().min(5),
        myUnion: z.union([z.number(), z.boolean()]),
      })
      .describe("My neat object schema");
    
    const jsonSchema = zodToJsonSchema(mySchema, "mySchema");
  2. Configure additionalProperties behavior

    master

    Zod's default behavior is to strip undeclared properties. To control how additionalProperties appears in the JSON Schema, use the following strategies:

    1. To allow additional properties:
      • Set removeAdditionalStrategy to "strict" (this allows them for any object not explicitly marked with .strict()).
      • OR keep the default "passthrough" and add .passthrough() to your Zod object schema.
    2. To remove the additionalProperties keyword entirely:
      • Set allowedAdditionalProperties to undefined (if you want it to be allowed/undefined).
      • Set rejectedAdditionalProperties to undefined (if you want it to be rejected/undefined).

    Note: These options are ignored if the schema uses .catchall(...).

  3. Configure schema names and definitions

    master

    You can specify a name for your schema to have it placed inside a definitions object and referenced via $ref. This can be done by passing a string as the second argument to zodToJsonSchema or by using the name property in the options object.

    To manually add recurring schemas into the definitions section for cleaner output, use the definitions option. This is useful for reducing redundancy in the generated JSON schema.

    const myRecurringSchema = z.string();
    const myObjectSchema = z.object({ a: myRecurringSchema, b: myRecurringSchema });
    
    const myJsonSchema = zodToJsonSchema(myObjectSchema, {
      definitions: { myRecurringSchema },
    });
  4. Understand the versioning policy

    master

    This package does not follow semantic versioning (SemVer).

    • Major/Minor versions: These reflect feature parity with the Zod package.
    • Breaking changes: API-breaking changes are kept to a minimum, but new features (like the options pattern) may be introduced in patch releases.
    • Zod v4 Compatibility: While v3.25 supports Zod v4 as a peer-dependency, it does not support v4 schemas. If you need to use Zod v3 schemas, use import { z } from "zod/v3".
  5. Use OverrideCallback to customize type parsing

    master

    The override option allows you to intercept the conversion process for specific Zod types. You can provide a function that returns a custom JSON schema, undefined to let the default parser handle it, or the ignoreOverride symbol to signal that the library should decide which parser to use.

    OverrideCallback signature: (def: ZodTypeDef, refs: Refs, seen: Seen | undefined, forceResolution?: boolean) => JsonSchema7Type | undefined | typeof ignoreOverride

  6. Use PostProcessCallback to modify generated schemas

    master

    The postProcess option allows you to run a function on the generated JSON schema after the conversion is complete. This is useful for adding metadata or transforming the structure.

    PostProcessCallback signature: (jsonSchema: JsonSchema7Type | undefined, def: ZodTypeDef, refs: Refs) => JsonSchema7Type | undefined

    Example: The built-in jsonDescription post-processor attempts to parse the Zod description as JSON and merge its properties into the schema.

    export const jsonDescription: PostProcessCallback = (jsonSchema, def) => {
      if (def.description) {
        try {
          return {
            ...jsonSchema,
            ...JSON.parse(def.description),
          };
        } catch {}
      }
    
      return jsonSchema;
    };
  7. Known issues and limitations in zod-to-json-schema

    master

    When using the library, be aware of the following technical limitations and behaviors:

    • OpenAI Target: The OpenAI target is experimental; certain option combinations may break compatibility.
    • .transform behavior: When using .transform, the JSON schema reflects the input side of the Zod schema rather than the output (z.infer). To allow any output type, use the effectStrategy: "any" option.
    • Object Key Types: JSON Schema only supports strings as object keys. Using z.record with non-string keys will result in the key type being ignored (except for z.enum, which is supported).
    • Relative JSON Pointers: While compatible with JSON Schema draft 2020-12, many resolvers do not support them.
    • Object Parser and .isOptional(): Since v3, the parser uses .isOptional() to determine required properties, which may call .safeParse with undefined. Ensure your preprocess and other effect callbacks are pure and do not throw errors.
    • JSON Schema 2020-12 Support: Official support is not yet implemented, but you can manually achieve compatibility by updating the returned schema's $schema field to "https://json-schema.org/draft/2020-12/schema#".
  8. Add metadata like examples using `jsonDescription`

    master

    Since Zod has limited support for JSON Schema meta-keys (like examples or title), you can use the jsonDescription helper in the postProcess option.

    This helper attempts to parse the Zod .describe() string as JSON and expands those keys directly into the resulting JSON Schema. This is the recommended way to add examples, title, or other custom metadata to your output.

    import zodToJsonSchema, { jsonDescription } from "zod-to-json-schema";
    
    const zodSchema = z.string().describe(
      JSON.stringify({
        title: "My string",
        description: "My description",
        examples: ["Foo", "Bar"],
        whatever: 123,
      }),
    );
    
    const jsonSchema = zodToJsonSchema(zodSchema, {
      postProcess: jsonDescription,
    });
  9. Use the `postProcess` callback to transform the final schema

    master

    The postProcess callback allows you to manipulate the fully generated JSON Schema. It receives the jsonSchema, the original Zod definition, and the refs object.

    Unlike override, you do not need to return a special symbol to keep the schema unchanged; simply return the jsonSchema as-is. If you return undefined, the schema will be filtered out.

    import zodToJsonSchema, { PostProcessCallback } from "zod-to-json-schema";
    
    const postProcess: PostProcessCallback = (
      jsonSchema,
      def,
      refs,
    ) => {
      if (!jsonSchema) {
        return jsonSchema;
      }
    
      // Example: Make all numbers nullable
      if ("type" in jsonSchema! && jsonSchema.type === "number") {
        jsonSchema.type = ["number", "null"];
      }
    
      return jsonSchema;
    };
    
    const jsonSchema = zodToJsonSchema(zodSchema, { postProcess });
  10. Use the `override` callback to modify schema generation

    master

    The override option allows you to intercept the schema generation process for specific paths. The callback receives the Zod definition, a reference object (containing currentPath), and other metadata.

    Crucial: If you do not want to override a specific item, you must return the ignoreOverride symbol. Returning undefined will cause the property to be removed from the resulting schema.

    import zodToJsonSchema, { ignoreOverride } from "zod-to-json-schema";
    
    zodToJsonSchema(
      z.object({
        ignoreThis: z.string(),
        overrideThis: z.string(),
        removeThis: z.string(),
      }),
      {
        override: (def, refs) => {
          const path = refs.currentPath.join("/");
    
          if (path === "#/properties/overrideThis") {
            return {
              type: "integer",
            };
          }
    
          if (path === "#/properties/removeThis") {
            return undefined;
          }
    
          // Important! Do not return `undefined` or void unless you want to remove the property from the resulting schema completely.
          return ignoreOverride;
        },
      },
    );
  11. Use zodToJsonSchema for basic conversion

    master

    To convert a Zod schema to a JSON schema, use the zodToJsonSchema function. It accepts the Zod schema as the first argument and an optional schema name as the second argument. Providing a schema name will wrap the output in a $ref pointing to a definition.

    import { z } from "zod";
    import { zodToJsonSchema } from "zod-to-json-schema";
    
    const mySchema = z
      .object({
        myString: z.string().min(5),
        myUnion: z.union([z.number(), z.boolean()]),
      })
      .describe("My neat object schema");
    
    const jsonSchema = zodToJsonSchema(mySchema, "mySchema");