colinhacks/zod · error · Error

Literal `undefined` cannot be represented in JSON Schema

Error message

Literal `undefined` cannot be represented in JSON Schema

What it means

Inside `literalProcessor` (json-schema-processors.ts:163), each literal value is inspected. A value of `undefined` is not representable, so when `ctx.unrepresentable === "throw"` the processor throws for `z.literal(undefined)`. With `unrepresentable: "any"` the undefined entry is silently dropped from the produced enum/const.

Source

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

  }
};

export const enumProcessor: Processor<schemas.$ZodEnum> = (schema, _ctx, json, _params) => {
  const def = schema._zod.def as schemas.$ZodEnumDef;
  const values = getEnumValues(def.entries);
  // Number enums can have both string and number values
  if (values.every((v) => typeof v === "number")) json.type = "number";
  if (values.every((v) => typeof v === "string")) json.type = "string";
  json.enum = values;
};

export const literalProcessor: Processor<schemas.$ZodLiteral> = (schema, ctx, json, _params) => {
  const def = schema._zod.def as schemas.$ZodLiteralDef<any>;
  const vals: (string | number | boolean | null)[] = [];
  for (const val of def.values) {
    if (val === undefined) {
      if (ctx.unrepresentable === "throw") {
        throw new Error("Literal `undefined` cannot be represented in JSON Schema");
      } else {
        // do not add to vals
      }
    } else if (typeof val === "bigint") {
      if (ctx.unrepresentable === "throw") {
        throw new Error("BigInt literals cannot be represented in JSON Schema");
      } else {
        vals.push(Number(val));
      }
    } else {
      vals.push(val);
    }
  }
  if (vals.length === 0) {
    // do nothing (an undefined literal was stripped)
  } else if (vals.length === 1) {
    const val = vals[0]!;
    json.type = val === null ? ("null" as const) : (typeof val as any);

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Pass `{ unrepresentable: "any" }` so the undefined literal is omitted from the output.
  2. Replace `z.literal(undefined)` with `z.undefined().optional()` or model absence via optionality (`.optional()`).
  3. Filter undefined out of the literal array before constructing the schema.

Example fix

// before
z.toJSONSchema(z.literal(undefined)); // throws
// after
z.toJSONSchema(z.literal(undefined), { unrepresentable: "any" });
// or model it as an optional field instead of a literal
Defensive patterns

Strategy: fallback

Validate before calling

const json = z.toJSONSchema(schema, { unrepresentable: "any" }); // undefined literal is dropped

Type guard

function hasUndefinedLiteral(s: z.ZodLiteral<any>): boolean {
  return [...s.values].some((v) => v === undefined);
}

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

When it happens

Trigger: `z.toJSONSchema()` over a schema containing `z.literal(undefined)`, or a multi-value `z.literal([x, undefined])`, with default options.

Common situations: Building a literal from a config value that happens to be undefined; unions of literals that include undefined to mean 'absent'.

Related errors


AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03). Data as JSON: /data/errors/631f1e922a00d60b.json. Report an issue: GitHub.