colinhacks/zod · error · Error

Invalid discriminated union option at index "${def.options.i

Error message

Invalid discriminated union option at index "${def.options.indexOf(o)}"

What it means

Thrown while building the discriminator lookup map in `$ZodDiscriminatedUnion` (the cached `disc` function). The option at the given index is an object, but it has no values registered for the specific discriminator key (`def.discriminator`). In other words, the option is discriminable in general but does not actually carry the discriminator field that the union was declared with, so it cannot be matched during parsing.

Source

Thrown at packages/zod/src/v4/core/schemas.ts:2390

        if (!pv || Object.keys(pv).length === 0)
          throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
        for (const [k, v] of Object.entries(pv!)) {
          if (!propValues[k]) propValues[k] = new Set();
          for (const val of v) {
            propValues[k].add(val);
          }
        }
      }
      return propValues;
    });

    const disc = util.cached(() => {
      const opts = def.options as $ZodTypeDiscriminable[];
      const map: Map<util.Primitive, $ZodType> = new Map();
      for (const o of opts) {
        const values = o._zod.propValues?.[def.discriminator];
        if (!values || values.size === 0)
          throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o)}"`);
        for (const v of values) {
          if (map.has(v)) {
            throw new Error(`Duplicate discriminator value "${String(v)}"`);
          }
          map.set(v, o);
        }
      }
      return map;
    });

    inst._zod.parse = (payload, ctx) => {
      const input = payload.value;
      if (!util.isObject(input)) {
        payload.issues.push({
          code: "invalid_type",

          expected: "object",
          input,

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Check the option at the reported index and add the discriminator field with a literal/enum value, e.g. `kind: z.literal('circle')`.
  2. Verify the discriminator string passed to `z.discriminatedUnion(discriminator, options)` exactly matches the field name present on EVERY option.
  3. Make the discriminator field an enum or literal on each option (a plain `z.string()` produces no discrete values and is not discriminable).

Example fix

// before
const Shape = z.discriminatedUnion('kind', [
  z.object({ kind: z.literal('circle'), radius: z.number() }),
  z.object({ shape: z.literal('square'), side: z.number() }), // wrong key
]);

// after
const Shape = z.discriminatedUnion('kind', [
  z.object({ kind: z.literal('circle'), radius: z.number() }),
  z.object({ kind: z.literal('square'), side: z.number() }),
]);
Defensive patterns

Strategy: validation

Validate before calling

function assertAllHaveDiscriminator(discriminator, options) {
  options.forEach((opt, i) => {
    const pv = opt._zod?.propValues;
    if (!pv || !pv[discriminator] || pv[discriminator].size === 0) {
      throw new Error(`Option ${i} missing discriminator "${discriminator}"`);
    }
  });
}

Type guard

function hasDiscriminatorValue(s, key): boolean {
  const pv = s._zod?.propValues;
  return !!pv && pv[key] instanceof Set && pv[key].size > 0;
}

Try / catch

try {
  const U = z.discriminatedUnion(discKey, options);
} catch (e) {
  if (e instanceof Error && e.message.includes('Invalid discriminated union option')) {
    // identify the option missing the discriminator key and fix its shape
  }
  throw e;
}

Prevention

When it happens

Trigger: Declaring `z.discriminatedUnion('kind', [A, B])` where the discriminator key is `'kind'` but one of A/B has no `kind` field, or its `kind` field is a non-literal type like `z.string()` (which yields no discrete propValues). Also: a typo in the discriminator string, or renaming the field on one option but not the others.

Common situations: Renaming a discriminator field on one variant and forgetting the others; copy-pasting variants and changing the field name only in the copy; using `z.string()` for the discriminator instead of `z.literal`/`z.enum`.

Related errors


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