colinhacks/zod · error · Error
A discriminator value for key
Error message
A discriminator value for key `${discriminator}` could not be extracted from all schema options What it means
Thrown at schema-construction time by z.discriminatedUnion (packages/zod/src/v3/types.ts:3200) when getDiscriminator returns no usable values for one of the option schemas. For a discriminated union, every option MUST expose the discriminator key as a literal (or enum/nativeEnum/const) value so Zod can build its lookup map; an option where the discriminator is optional, missing, or a dynamic type yields an empty value list and trips this error.
Solutions
- Ensure every option defines the discriminator key as `z.literal('value')` (or z.enum / z.nativeEnum) — not z.string(), not optional.
- Find the offending option by logging each option's shape[discriminator] before constructing the union.
- If a branch genuinely has no fixed discriminator value, restructure the schema as a plain z.union instead of z.discriminatedUnion.
- If the discriminator lives behind a transform or preprocess, hoist it out so it is a direct literal field.
Example fix
// before
const A = z.object({ type: z.literal('a'), x: z.number() });
const B = z.object({ y: z.string() }); // missing discriminator
const U = z.discriminatedUnion('type', [A, B]);
// after
const B = z.object({ type: z.literal('b'), y: z.string() });
const U = z.discriminatedUnion('type', [A, B]); Defensive patterns
Strategy: validation
Validate before calling
import { z, ZodType, ZodFirstPartyTypeKind } from 'zod';
function getLiteralValues(s: ZodType): unknown[] {
const def = (s as any)._def;
if (!def) return [];
switch (def.typeName) {
case ZodFirstPartyTypeKind.ZodLiteral:
return [def.value];
case ZodFirstPartyTypeKind.ZodEnum:
return [...def.values];
case ZodFirstPartyTypeKind.ZodNativeEnum:
return Object.values(def.values).filter((v: unknown) => typeof v === 'string' || typeof v === 'number');
case ZodFirstPartyTypeKind.ZodUndefined:
return [undefined];
case ZodFirstPartyTypeKind.ZodNull:
return [null];
default:
return [];
}
}
function assertAllOptionsDiscriminate(discriminator: string, options: z.ZodType[]) {
const missing = options.filter((o) => getLiteralValues((o as any).shape?.[discriminator]).length === 0);
if (missing.length) {
throw new Error(`Option(s) at indices ${missing.map((_, i) => i)} lack a literal discriminator '${discriminator}'`);
}
} Type guard
function isLiteralField(field: unknown): boolean {
if (!field || typeof field !== 'object') return false;
const tn = (field as any)._def?.typeName;
return (
tn === 'ZodLiteral' ||
tn === 'ZodEnum' ||
tn === 'ZodNativeEnum' ||
tn === 'ZodUndefined' ||
tn === 'ZodNull' ||
tn === 'ZodBoolean' // boolean yields true/false literals
);
} Try / catch
null
Prevention
- Always define the discriminator as z.literal('value') on every branch.
- If a branch has no fixed tag, use z.union instead of z.discriminatedUnion.
- Keep discriminators as required (non-optional) literal fields.
- Build discriminated unions incrementally so the offending option is easy to localise.
When it happens
Trigger: Calling `z.discriminatedUnion('type', [A, B, C])` where at least one option lacks the `type` field, has it as `z.string()` instead of a literal, marks it `.optional()`, or nests the discriminator inside a transform/lazy. Any option whose discriminator shape is not a plain literal/enum fails extraction.
Common situations: Forgetting to add the discriminator field to one branch of a tagged union; typing the discriminator as z.string() instead of z.literal('x'); making the discriminator optional in one variant; refactoring shared base objects where the discriminator key was dropped; mixing in a ZodAny or ZodUnknown as the discriminator.
Related errors
- Invalid discriminated union option at index
- 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/aea2b34b4dd73d9a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v3/types.ts:3200
static create<
Discriminator extends string,
Types extends readonly [
ZodDiscriminatedUnionOption<Discriminator>,
...ZodDiscriminatedUnionOption<Discriminator>[],
],
>(
discriminator: Discriminator,
options: Types,
params?: RawCreateParams
): ZodDiscriminatedUnion<Discriminator, Types> {
// Get all the valid discriminator values
const optionsMap: Map<Primitive, Types[number]> = new Map();
// try {
for (const type of options) {
const discriminatorValues = getDiscriminator(type.shape[discriminator]);
if (!discriminatorValues.length) {
throw new Error(
`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`
);
}
for (const value of discriminatorValues) {
if (optionsMap.has(value)) {
throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);
}
optionsMap.set(value, type);
}
}
return new ZodDiscriminatedUnion<
Discriminator,
// DiscriminatorValue,
Types
>({
typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,View on GitHub (pinned to 2d90846af9)