colinhacks/zod · error · Error
Invalid discriminated union option at index
Error message
Invalid discriminated union option at index "${def.options.indexOf(o)}" What it means
Thrown inside the cached disc() function of a discriminated union when, for a given option o, `o._zod.propValues?.[def.discriminator]` is absent or empty. Unlike error 42 (which fires when an option has no propValues at all), this fires specifically when the option does have propValues but none are published under the configured discriminator key — i.e. the option exists but does not declare a literal value at that exact key.
Solutions
- Make sure every option has a literal value (z.literal() or z.enum()) at the exact discriminator key.
- Double-check the discriminator key spelling matches across all options.
- Avoid marking the discriminator field .optional(); every option must commit to a concrete literal.
- Print def.discriminator and each option's propValues keys to locate the mismatched branch.
Example fix
// before
const U = z.discriminatedUnion("type", [
z.object({ type: z.literal("circle"), r: z.number() }),
z.object({ type: z.string(), w: z.number(), h: z.number() }), // not a literal
]);
// after
const U = z.discriminatedUnion("type", [
z.object({ type: z.literal("circle"), r: z.number() }),
z.object({ type: z.literal("rect"), w: z.number(), h: z.number() }),
]); Defensive patterns
Strategy: validation
Validate before calling
function buildDiscUnion(disc: string, options: z.ZodObject[]) {
for (const o of options) {
const vals = (o as any)._zod.propValues?.[disc];
if (!vals || vals.size === 0) {
throw new Error(`Option is missing a literal at discriminator key "${disc}".`);
}
}
return z.discriminatedUnion(disc, options);
} Type guard
function optionHasDiscriminator(option: unknown, disc: string): boolean {
const vals = (option as any)?._zod?.propValues?.[disc];
return !!vals && vals.size > 0;
} Prevention
- Ensure every option declares z.literal()/z.enum() at the exact discriminator key.
- Keep discriminator keys consistent across options — single source of truth for the key name.
- Never mark the discriminator field optional.
When it happens
Trigger: Calling z.discriminatedUnion("kind", [...]) where at least one option's object shape lacks a literal at the "kind" field, or declares it as a non-literal type (z.string() instead of z.literal()). Also triggered by typos in the discriminator key name ("type" vs "kind"), or by an option whose discriminator field is optional (z.literal("a").optional()) so propValues may be treated as not contributing a value.
Common situations: Discriminator key renamed in one branch but not others; one option omits the discriminator field entirely; field declared as z.string() instead of z.literal()/z.enum(); copy-paste of option schemas where the literal was forgotten; optional discriminator fields.
Related errors
- A discriminator value for key
- Discriminator property
- Duplicate discriminator value
- Invalid discriminated union option at index
- Cannot create literal schema with no valid values
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/c97358a769ab77be.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/core/schemas.ts:2407
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 2d90846af9)