colinhacks/zod · error · Error

Conditional schemas (if/then/else) are not supported

Error message

Conditional schemas (if/then/else) are not supported

What it means

Thrown by convertBaseSchema when the input contains any of `if`, `then`, or `else`. Conditional schema selection is a runtime branching construct with no direct Zod equivalent; the converter refuses rather than guessing an approximation.

Source

Thrown at packages/zod/src/v4/classic/from-json-schema.ts:162

}

function convertBaseSchema(schema: JSONSchema.JSONSchema, ctx: ConversionContext): ZodType {
  // Handle unsupported features
  if (schema.not !== undefined) {
    // Special case: { not: {} } represents never
    if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
      return z.never();
    }
    throw new Error("not is not supported in Zod (except { not: {} } for never)");
  }
  if (schema.unevaluatedItems !== undefined) {
    throw new Error("unevaluatedItems is not supported");
  }
  if (schema.unevaluatedProperties !== undefined) {
    throw new Error("unevaluatedProperties is not supported");
  }
  if (schema.if !== undefined || schema.then !== undefined || schema.else !== undefined) {
    throw new Error("Conditional schemas (if/then/else) are not supported");
  }
  if (schema.dependentSchemas !== undefined || schema.dependentRequired !== undefined) {
    throw new Error("dependentSchemas and dependentRequired are not supported");
  }

  // Handle $ref
  if (schema.$ref) {
    const refPath = schema.$ref;
    if (ctx.refs.has(refPath)) {
      return ctx.refs.get(refPath)!;
    }

    if (ctx.processing.has(refPath)) {
      // Circular reference - use lazy
      return z.lazy(() => {
        if (!ctx.refs.has(refPath)) {
          throw new Error(`Circular reference not resolved: ${refPath}`);
        }

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Re-model the conditional as a discriminated union: split into one object per branch and select by the discriminator value.
  2. Move the conditional logic into a post-conversion `.refine()` / `.superRefine()` on the resulting Zod schema.
  3. Inline the branches into separate `$defs` and let the caller pick the appropriate schema.

Example fix

// before
{
  "if":   { "properties": { "kind": { "const": "a" } }, "required": ["kind"] },
  "then": { "properties": { "a": { "type": "string" } } },
  "else": { "properties": { "b": { "type": "number" } } }
}

// after (build a discriminated union instead)
z.discriminatedUnion('kind', [
  z.object({ kind: z.literal('a'), a: z.string() }),
  z.object({ kind: z.literal('b'), b: z.number() }),
]);
Defensive patterns

Strategy: fallback

Validate before calling

function hasConditional(schema: any): boolean {
  return schema?.if !== undefined || schema?.then !== undefined || schema?.else !== undefined;
}

Prevention

When it happens

Trigger: Schemas using `if/then/else` to apply constraints conditionally, e.g. `{ if: { properties: { kind: { const: 'a' } } }, then: { ... }, else: { ... } }`.

Common situations: Polymorphic API schemas; JSON Schemas authored for full validators (Ajv); legacy draft-7 schemas migrating to Zod.

Related errors


AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03). Data as JSON: /data/errors/785e4af02fa947b7.json. Report an issue: GitHub.