colinhacks/zod · error · Error

BigInt literals cannot be represented in JSON Schema

Error message

BigInt literals cannot be represented in JSON Schema

What it means

In `literalProcessor` (json-schema-processors.ts:173), a literal value whose `typeof` is `"bigint"` cannot be encoded in JSON. With `ctx.unrepresentable === "throw"` (default) it throws; with `unrepresentable: "any"` it falls back to `vals.push(Number(val))` (coerces to a number, losing precision for large values).

Source

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

  // 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);
    if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
      json.enum = [val];
    } else {
      json.const = val;
    }
  } else {

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Pass `{ unrepresentable: "any" }` (note: large bigints will be lossy via `Number()`).
  2. Replace the bigint literal with a string literal (`z.literal("5")`) and convert at the boundary to preserve precision.
  3. Use a string regex schema (`z.string().regex(/^\d+$/)`) instead of a bigint literal for the contract.

Example fix

// before
z.toJSONSchema(z.literal(BigInt(5))); // throws
// after
z.toJSONSchema(z.literal("5")); // -> { type: "string", const: "5" }
Defensive patterns

Strategy: fallback

Validate before calling

// For lossless contracts, encode bigints as string literals.
const contractSchema = z.literal("5");
const json = z.toJSONSchema(contractSchema);
// Or opt into lossy numeric coercion:
// z.toJSONSchema(schema, { unrepresentable: "any" });

Type guard

function hasBigIntLiteral(s: z.ZodLiteral<any>): boolean {
  return [...s.values].some((v) => typeof v === "bigint");
}

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 `z.literal(BigInt(5))` or `z.literal([1n, 2n])`, with default options.

Common situations: Using bigint literals for IDs/bitmasks and then generating a contract; converting schemas built from database bigint constants.

Related errors


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