{"record":{"id":"aea2b34b4dd73d9a","repo":"colinhacks/zod","slug":"a-discriminator-value-for-key-discriminator-c","errorCode":null,"errorMessage":"A discriminator value for key `${discriminator}` could not be extracted from all schema options","messagePattern":"A discriminator value for key `(.+?)` could not be extracted from all schema options","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v3/types.ts","lineNumber":3200,"sourceCode":"  static create<\n    Discriminator extends string,\n    Types extends readonly [\n      ZodDiscriminatedUnionOption<Discriminator>,\n      ...ZodDiscriminatedUnionOption<Discriminator>[],\n    ],\n  >(\n    discriminator: Discriminator,\n    options: Types,\n    params?: RawCreateParams\n  ): ZodDiscriminatedUnion<Discriminator, Types> {\n    // Get all the valid discriminator values\n    const optionsMap: Map<Primitive, Types[number]> = new Map();\n\n    // try {\n    for (const type of options) {\n      const discriminatorValues = getDiscriminator(type.shape[discriminator]);\n      if (!discriminatorValues.length) {\n        throw new Error(\n          `A discriminator value for key \\`${discriminator}\\` could not be extracted from all schema options`\n        );\n      }\n      for (const value of discriminatorValues) {\n        if (optionsMap.has(value)) {\n          throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);\n        }\n\n        optionsMap.set(value, type);\n      }\n    }\n\n    return new ZodDiscriminatedUnion<\n      Discriminator,\n      // DiscriminatorValue,\n      Types\n    >({\n      typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,","sourceCodeStart":3182,"sourceCodeEnd":3218,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v3/types.ts#L3182-L3218","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst A = z.object({ type: z.literal('a'), x: z.number() });\nconst B = z.object({ y: z.string() }); // missing discriminator\nconst U = z.discriminatedUnion('type', [A, B]);\n\n// after\nconst B = z.object({ type: z.literal('b'), y: z.string() });\nconst U = z.discriminatedUnion('type', [A, B]);","handlingStrategy":"validation","validationCode":"import { z, ZodType, ZodFirstPartyTypeKind } from 'zod';\n\nfunction getLiteralValues(s: ZodType): unknown[] {\n  const def = (s as any)._def;\n  if (!def) return [];\n  switch (def.typeName) {\n    case ZodFirstPartyTypeKind.ZodLiteral:\n      return [def.value];\n    case ZodFirstPartyTypeKind.ZodEnum:\n      return [...def.values];\n    case ZodFirstPartyTypeKind.ZodNativeEnum:\n      return Object.values(def.values).filter((v: unknown) => typeof v === 'string' || typeof v === 'number');\n    case ZodFirstPartyTypeKind.ZodUndefined:\n      return [undefined];\n    case ZodFirstPartyTypeKind.ZodNull:\n      return [null];\n    default:\n      return [];\n  }\n}\n\nfunction assertAllOptionsDiscriminate(discriminator: string, options: z.ZodType[]) {\n  const missing = options.filter((o) => getLiteralValues((o as any).shape?.[discriminator]).length === 0);\n  if (missing.length) {\n    throw new Error(`Option(s) at indices ${missing.map((_, i) => i)} lack a literal discriminator '${discriminator}'`);\n  }\n}","typeGuard":"function isLiteralField(field: unknown): boolean {\n  if (!field || typeof field !== 'object') return false;\n  const tn = (field as any)._def?.typeName;\n  return (\n    tn === 'ZodLiteral' ||\n    tn === 'ZodEnum' ||\n    tn === 'ZodNativeEnum' ||\n    tn === 'ZodUndefined' ||\n    tn === 'ZodNull' ||\n    tn === 'ZodBoolean' // boolean yields true/false literals\n  );\n}","tryCatchPattern":"null","preventionTips":["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."],"tags":["discriminated-union","schema-construction","literal","discriminator"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}