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
`z.custom()` validates via an opaque user-supplied function whose contract cannot be introspected, so `customProcessor` (json-schema-processors.ts:244) cannot emit a meaningful JSON Schema and throws when `ctx.unrepresentable === "throw"` (default).
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 912f0f51b0)
Solutions
- Pass `{ unrepresentable: "any" }` so the custom node becomes an unconstrained schema.
- Replace `z.custom(fn)` with a concrete schema (e.g. `z.email()`, `z.string().regex(...)`) that JSON Schema can express.
- Use `z.toJSONSchema(schema, { override: (s) => ... })` to hand-author the fragment for the custom node.
Example fix
// before
z.toJSONSchema(z.object({ x: z.custom((v) => typeof v === "string") })); // throws
// after
z.toJSONSchema(z.object({ x: z.string() })); Defensive patterns
Strategy: fallback
Validate before calling
// Replace opaque custom checks with declarative schemas for contracts.
const contractSchema = z.object({ email: z.email() });
const json = z.toJSONSchema(contractSchema);
// Or: z.toJSONSchema(schema, { unrepresentable: "any" }); Type guard
function usesCustom(schema: z.ZodType): boolean {
return schema._zod.traits.has("$ZodCustom");
} Try / catch
try {
return z.toJSONSchema(schema);
} catch (e) {
if (e instanceof Error && /cannot be represented in JSON Schema/.test(e.message)) {
return z.toJSONSchema(schema, { unrepresentable: "any" });
}
throw e;
} Prevention
- Avoid `z.custom()` in schemas used for contracts; prefer built-in or regex-based schemas.
- Use the `override` callback of `z.toJSONSchema` to hand-author fragments for unavoidable custom nodes.
- Keep runtime-validation schemas (with custom) separate from contract schemas.
When it happens
Trigger: `z.toJSONSchema()` over a schema containing `z.custom<T>((x) => ...)`, with default options. Common when reusing a hand-written guard inside an otherwise-declarative schema.
Common situations: Mixing declarative Zod schemas with bespoke runtime checks (e.g. `z.custom(isEmailAddress)`) and then generating docs/OpenAPI.
Related errors
- BigInt cannot be represented in JSON Schema
- Symbols cannot be represented in JSON Schema
- Undefined cannot be represented in JSON Schema
- Void cannot be represented in JSON Schema
- Date cannot be represented in JSON Schema
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/f7f46596d5985a28.json.
Report an issue: GitHub.