colinhacks/zod · error · Error

Custom types cannot be represented in JSON Schema

Error message

Custom types cannot be represented in JSON Schema

What it means

Thrown by toJSONSchema() when a ZodCustom schema (z.custom(...)) is encountered with unrepresentable 'throw' (default). A custom check carries arbitrary validation logic with no declarative JSON Schema equivalent, so the converter cannot faithfully emit a constraint.

Solutions

  1. Pass { unrepresentable: 'any' } to toJSONSchema() so the custom field becomes {}.
  2. Express what you can declaratively (z.string().email(), patterns, min/max) and keep only truly-unrepresentable logic in z.custom — accepting that JSON Schema loses it.
  3. Use the override() hook of toJSONSchema() to inject a custom JSON Schema fragment for the field, decoupling runtime validation from the exported contract.

Example fix

// before (throws)
const Even = z.custom((x) => typeof x === 'number' && x % 2 === 0);
z.toJSONSchema(Even);

// after
const Even = z.custom((x) => typeof x === 'number' && x % 2 === 0);
z.toJSONSchema(Even, { unrepresentable: 'any' }); // {}
// or declare the representable part and refine separately
const Even = z.number().refine((x) => x % 2 === 0);
Defensive patterns

Strategy: try-catch

Validate before calling

const opts = schemaContains(schema, (s) => s._zod.def.type === 'custom')
  ? { unrepresentable: 'any' }
  : {};
const json = z.toJSONSchema(schema, opts);

Type guard

function hasCustom(schema) {
  return schema._zod.def.type === 'custom';
}

Try / catch

try {
  return z.toJSONSchema(schema);
} catch (e) {
  if (e.message === 'Custom types cannot be represented in JSON Schema') {
    return z.toJSONSchema(schema, { unrepresentable: 'any' });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling toJSONSchema() on a schema containing z.custom(() => check(...)). Composing a custom validator into a model and then exporting the model to JSON Schema/OpenAPI.

Common situations: Refining schemas with business rules (z.custom / .refine) and expecting the JSON Schema output to encode those rules.

Related errors


AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11). Data as JSON: /api/errors/f7f46596d5985a28. Report an issue: GitHub.

Appendix: source

Thrown at packages/zod/src/v4/core/json-schema-processors.ts:246

    if (mime.length === 1) {
      file.contentMediaType = mime[0]!;
      Object.assign(_json, file);
    } else {
      Object.assign(_json, file); // shared props at root
      _json.anyOf = mime.map((m) => ({ contentMediaType: m })); // only contentMediaType differs
    }
  } else {
    Object.assign(_json, file);
  }
};

export const successProcessor: Processor<schemas.$ZodSuccess> = (_schema, _ctx, json, _params) => {
  (json as JSONSchema.BooleanSchema).type = "boolean";
};

export const customProcessor: Processor<schemas.$ZodCustom> = (_schema, ctx, _json, _params) => {
  if (ctx.unrepresentable === "throw") {
    throw new Error("Custom types cannot be represented in JSON Schema");
  }
};

export const functionProcessor: Processor<schemas.$ZodFunction> = (_schema, ctx, _json, _params) => {
  if (ctx.unrepresentable === "throw") {
    throw new Error("Function types cannot be represented in JSON Schema");
  }
};

export const transformProcessor: Processor<schemas.$ZodTransform> = (_schema, ctx, _json, _params) => {
  if (ctx.unrepresentable === "throw") {
    throw new Error("Transforms cannot be represented in JSON Schema");
  }
};

export const mapProcessor: Processor<schemas.$ZodMap> = (_schema, ctx, _json, _params) => {
  if (ctx.unrepresentable === "throw") {
    throw new Error("Map cannot be represented in JSON Schema");

View on GitHub (pinned to 2d90846af9)