colinhacks/zod · error · Error
A discriminator value for key `${discriminator}` could not b
Error message
A discriminator value for key `${discriminator}` could not be extracted from all schema options What it means
Thrown by z.discriminatedUnion when getDiscriminator returns no extractable literal value for one of the options. The discriminator field must resolve to a concrete primitive (literal string/number/boolean, a single-value enum, etc.) on every option so the union can dispatch at runtime.
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 912f0f51b0)
Solutions
- On every option, set the discriminator field to a concrete literal: `type: z.literal('a')`.
- If the tag is optional in some branch, decide on a default via z.literal and remove optionality at the discriminator key.
- Verify the discriminator key string actually exists on each option's `.shape`.
Example fix
// before
z.discriminatedUnion('type', [
z.object({ type: z.string(), a: z.string() }), // no literal -> error
z.object({ type: z.literal('b'), b: z.number() }),
]);
// after
z.discriminatedUnion('type', [
z.object({ type: z.literal('a'), a: z.string() }),
z.object({ type: z.literal('b'), b: z.number() }),
]); Defensive patterns
Strategy: validation
Validate before calling
import { getDiscriminator } from "zod"; // or replicate
function verifyDiscriminator(disc: string, options: z.ZodObject<any>[]) {
for (const o of options) {
const vals = o.shape[disc];
if (!vals || !('value' in vals._def) && vals._def.values == null) {
throw new Error(`Option missing literal discriminator '${disc}'`);
}
}
} Type guard
function hasLiteralDiscriminator<K extends string>(
o: z.ZodObject<any>,
key: K
): boolean {
const f = (o.shape as any)[key];
return !!f && (f instanceof z.ZodLiteral || f instanceof z.ZodEnum || f instanceof z.ZodNativeEnum);
} Prevention
- Always type the discriminator field as z.literal for each branch.
- Unit-test discriminated unions at construction time.
- Keep the discriminator non-optional and non-defaulted.
When it happens
Trigger: z.discriminatedUnion('type', [A, B]) where one option's `type` field is a plain z.string() (no literal), optional and missing, an unsupported type (e.g. z.any(), z.array()), or nested behind an intermediary that getDiscriminator cannot unwrap.
Common situations: Building a tagged union where one branch forgot to constrain the tag (used `type: z.string()` instead of `type: z.literal('a')`); renaming a tag literal and forgetting a branch; discriminator hidden under z.optional or z.default.
Related errors
- Discriminator property ${String(discriminator)} has duplicat
- Can't use "invalid_type_error" or "required_error" in conjun
- Synchronous parse encountered promise.
- You must pass an array of schemas to z.tuple([ ... ])
- Async refinement encountered during synchronous parse operat
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/aea2b34b4dd73d9a.json.
Report an issue: GitHub.