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

Thrown by the literalProcessor when a ZodLiteral's accepted values include the JavaScript undefined value and unrepresentable is 'throw' (default). JSON Schema const/enum cannot express undefined (it is not JSON), so the converter refuses rather than emit a const: undefined that downstream tools would misread.

Solutions

  1. Pass { unrepresentable: 'any' } to toJSONSchema(); the undefined entry is dropped silently from the literal's values.
  2. For the exported contract, replace z.literal(undefined) with z.literal(null) or omit the key (z.optional) to express absence portably.
  3. Branch the schema so the export variant contains only JSON-valid literals.

Example fix

// before (throws)
const Schema = z.literal(undefined);
z.toJSONSchema(Schema);

// after
const Schema = z.literal(undefined);
z.toJSONSchema(Schema, { unrepresentable: 'any' }); // yields {}
// or use null for the external contract
const External = z.literal(null);
Defensive patterns

Strategy: try-catch

Validate before calling

const opts = schemaContains(schema, (s) =>
  s._zod.def.type === 'literal' && [...s.values].includes(undefined))
  ? { unrepresentable: 'any' }
  : {};
const json = z.toJSONSchema(schema, opts);

Type guard

function hasUndefinedLiteral(schema) {
  if (schema._zod.def.type !== 'literal') return false;
  const def = schema._zod.def;
  return Array.isArray(def.values) && def.values.includes(undefined);
}

Try / catch

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

Prevention

When it happens

Trigger: Calling toJSONSchema() on z.literal(undefined) or z.literal([undefined, 'x']). A schema that discriminates on 'this literal is undefined' for an external contract.

Common situations: Using undefined as a sentinel literal value internally, then exporting the same schema for API documentation or to drive a JSON-Schema validator.

Related errors


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

Appendix: 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 2d90846af9)