{"record":{"id":"7a266e4ca8109c66","repo":"colinhacks/zod","slug":"dynamic-catch-values-are-not-supported-in-json-sch","errorCode":null,"errorMessage":"Dynamic catch values are not supported in JSON Schema","messagePattern":"Dynamic catch values are not supported in JSON Schema","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/core/json-schema-processors.ts","lineNumber":528,"sourceCode":"export const prefaultProcessor: Processor<schemas.$ZodPrefault> = (schema, ctx, json, params) => {\n  const def = schema._zod.def as schemas.$ZodPrefaultDef;\n  process(def.innerType, ctx as any, params);\n  const seen = ctx.seen.get(schema)!;\n  seen.ref = def.innerType;\n  if (ctx.io === \"input\") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));\n};\n\nexport const catchProcessor: Processor<schemas.$ZodCatch> = (schema, ctx, json, params) => {\n  const def = schema._zod.def as schemas.$ZodCatchDef;\n  process(def.innerType, ctx as any, params);\n  const seen = ctx.seen.get(schema)!;\n  seen.ref = def.innerType;\n  let catchValue: any;\n  try {\n    catchValue = def.catchValue(undefined as any);\n  } catch {\n    if (ctx.unrepresentable === \"throw\") {\n      throw new Error(\"Dynamic catch values are not supported in JSON Schema\");\n    }\n    return;\n  }\n  json.default = catchValue;\n};\n\nexport const pipeProcessor: Processor<schemas.$ZodPipe> = (schema, ctx, _json, params) => {\n  const def = schema._zod.def as schemas.$ZodPipeDef;\n  const inIsTransform = def.in._zod.traits.has(\"$ZodTransform\");\n  const innerType = ctx.io === \"input\" ? (inIsTransform ? def.out : def.in) : def.out;\n  process(innerType, ctx as any, params);\n  const seen = ctx.seen.get(schema)!;\n  seen.ref = innerType;\n};\n\nexport const readonlyProcessor: Processor<schemas.$ZodReadonly> = (schema, ctx, json, params) => {\n  const def = schema._zod.def as schemas.$ZodReadonlyDef;\n  process(def.innerType, ctx as any, params);","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v4/core/json-schema-processors.ts#L510-L546","documentation":"Thrown by the catchProcessor when a ZodCatch schema uses a *function* catch value (z.catch(() => computeDefault())) and that function throws when invoked, while unrepresentable is 'throw' (default). The converter tries to call the catch function to obtain a concrete default for JSON Schema's `default` keyword; a throwing or side-effecting catch function cannot be serialised and is rejected.","triggerScenarios":"Defining z.catch(() => { throw new Error('no default') }) or z.catch(() => someUndefinedVar.foo) and calling toJSONSchema() on the schema. A catch function that depends on runtime state unavailable during schema export (env, DB, request context).","commonSituations":"Catch values that read config at call time, randomised fallbacks, or catches that legitimately only make sense during parsing — all of which break static JSON Schema generation.","solutions":["Pass { unrepresentable: 'any' } to toJSONSchema(); the dynamic catch is skipped (no `default` emitted) instead of throwing.","Provide a static catch value (a literal or pure expression) for the schema used in JSON Schema export, and keep the dynamic catch only in the runtime schema.","Make the catch function total — never throw, always return a JSON-serialisable constant — so the converter can sample it safely."],"exampleFix":"// before (throws if readEnv throws)\nconst Schema = z.string().catch(() => readEnv('DEFAULT')!);\nz.toJSONSchema(Schema);\n\n// after (static value for export)\nconst ExportSchema = z.string().catch('fallback');\nz.toJSONSchema(ExportSchema);\n// { type: 'string', default: 'fallback' }\n// or skip it\nz.toJSONSchema(Schema, { unrepresentable: 'any' });","handlingStrategy":"try-catch","validationCode":"// Detect dynamic catch functions before exporting.\nfunction isDynamicCatch(schema) {\n  return schema._zod.def.type === 'catch' && typeof schema._zod.def.catchValue === 'function';\n}\nconst opts = schemaContains(schema, isDynamicCatch)\n  ? { unrepresentable: 'any' }\n  : {};\nconst json = z.toJSONSchema(schema, opts);","typeGuard":"function hasDynamicCatch(schema) {\n  if (schema._zod.def.type !== 'catch') return false;\n  // catchValue is always a function in the def; test whether it throws when sampled\n  try {\n    schema._zod.def.catchValue(undefined);\n    return false;\n  } catch {\n    return true;\n  }\n}","tryCatchPattern":"try {\n  return z.toJSONSchema(schema);\n} catch (e) {\n  if (e.message === 'Dynamic catch values are not supported in JSON Schema') {\n    // Omit the default by allowing the any fallback\n    return z.toJSONSchema(schema, { unrepresentable: 'any' });\n  }\n  throw e;\n}","preventionTips":["Use static catch values (literals) in schemas you export; keep dynamic catch functions runtime-only.","If a catch must read runtime state, make it total (never throw) and return a JSON-serialisable constant.","Pass { unrepresentable: 'any' } when exporting schemas that may include dynamic catches."],"tags":["json-schema","catch","default","unrepresentable","to-json-schema"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}