colinhacks/zod · error · Error
Discriminator property ${String(discriminator)} has duplicat
Error message
Discriminator property ${String(discriminator)} has duplicate value ${String(value)} What it means
Thrown by z.discriminatedUnion when two or more options resolve to the same discriminator value. The dispatch map keys must be unique or the union cannot pick a branch deterministically.
Source
Thrown at packages/zod/src/v3/types.ts:3206
>(
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,
discriminator,
options,
optionsMap,
...processCreateParams(params),
});
}View on GitHub (pinned to 912f0f51b0)
Solutions
- Make every option's discriminator value(s) mutually exclusive.
- If the same payload should validate against multiple shapes, model it as z.intersection or merge the schemas instead of a discriminated union.
- List the discriminator values per branch and dedupe.
Example fix
// before
z.discriminatedUnion('type', [
z.object({ type: z.literal('x'), a: z.string() }),
z.object({ type: z.literal('x'), b: z.number() }), // duplicate 'x'
]);
// after
z.discriminatedUnion('type', [
z.object({ type: z.literal('x'), a: z.string() }),
z.object({ type: z.literal('y'), b: z.number() }),
]); Defensive patterns
Strategy: validation
Validate before calling
function assertDistinctDiscriminators(disc: string, options: z.ZodObject<any>[]) {
const seen = new Set<unknown>();
for (const o of options) {
const f = o.shape[disc];
const values = f instanceof z.ZodLiteral ? [f.value]
: f instanceof z.ZodEnum ? f.options : [];
for (const v of values) {
if (seen.has(v)) throw new Error(`Duplicate discriminator: ${String(v)}`);
seen.add(v);
}
}
} Prevention
- Maintain a single source of truth for tag values (e.g. a z.enum) and derive literals from it.
- Add a construction-time test enumerating discriminator values per branch.
- Avoid enums in the discriminator field unless all values are mutually exclusive across branches.
When it happens
Trigger: Two options whose discriminator field uses the same z.literal, or whose literals/enums collapse to the same primitive, e.g. `z.object({ type: z.literal('x') })` appearing twice, or `z.enum(['x','y'])` overlapping another option's `z.literal('x')`.
Common situations: Copy-pasting a branch and forgetting to change the tag; merging schemas where tags collide; an option that uses an enum sharing a value with another branch's literal.
Related errors
- A discriminator value for key `${discriminator}` could not b
- 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/1dfe069b2436a50c.json.
Report an issue: GitHub.