colinhacks/zod · error · Error

Not a ZodError

Error message

Not a ZodError: ${value}

What it means

Thrown by the static guard ZodError.assert(value) at packages/zod/src/v3/ZodError.ts:278 when the value passed in is not an instance of ZodError. The method is a runtime narrowing helper (signature: asserts value is ZodError), so calling it on anything else — a plain Error, a rejected promise reason, a custom error object — fails the assertion and throws a generic Error. It exists to let callers verify that a caught value really carries Zod issue data before reading .issues off it.

Solutions

  1. Guard with `value instanceof ZodError` before calling ZodError.assert(value), and handle the non-Zod branch separately.
  2. In catch blocks, branch on the error type: `if (e instanceof ZodError) { ... } else { throw e; }` instead of unconditionally asserting.
  3. Trace where the non-ZodError value originates and ensure that code path only throws ZodErrors (e.g. it parses through a schema) or rethrows other errors.
  4. If you only need the issues list, use safeParse()/safeParseAsync() so failures never throw in the first place.

Example fix

// before
try {
  schema.parse(data);
} catch (e) {
  ZodError.assert(e);
  console.log(e.issues);
}

// after
try {
  schema.parse(data);
} catch (e) {
  if (e instanceof ZodError) {
    console.log(e.issues);
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: type-guard

Validate before calling

null

Type guard

import { ZodError } from 'zod';

function isZodError(value: unknown): value is ZodError {
  return value instanceof ZodError;
}

// usage:
// try { schema.parse(x); } catch (e) {
//   if (isZodError(e)) handleIssues(e.issues);
//   else throw e;
// }

Try / catch

try {
  schema.parse(data);
} catch (e) {
  if (e instanceof ZodError) {
    // structured issue data is safe to read here
    reportIssues(e.issues);
  } else {
    throw e; // never swallow unknown errors
  }
}

Prevention

When it happens

Trigger: Calling ZodError.assert(someValue) where someValue is a generic Error, a string, undefined, or any non-ZodError thrown from code unrelated to schema parsing. Common in catch blocks that indiscriminately pass the caught value to assert() without first checking instanceof.

Common situations: Mixing Zod with other validation libraries where a different library's error propagates into the same catch block; wrapping third-party code that throws non-Zod errors; refactoring that changes which errors a function can throw; using assert() on error causes from network/IO layers that wrap failures in their own Error subclasses.

Related errors


AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11). Data as JSON: /api/errors/0aa0664eed08f585. Report an issue: GitHub.

Appendix: source

Thrown at packages/zod/src/v3/ZodError.ts:278

            }
            i++;
          }
        }
      }
    };

    processError(this);
    return fieldErrors;
  }

  static create = (issues: ZodIssue[]) => {
    const error = new ZodError(issues);
    return error;
  };

  static assert(value: unknown): asserts value is ZodError {
    if (!(value instanceof ZodError)) {
      throw new Error(`Not a ZodError: ${value}`);
    }
  }

  override toString() {
    return this.message;
  }
  override get message() {
    return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
  }

  get isEmpty(): boolean {
    return this.issues.length === 0;
  }

  addIssue = (sub: ZodIssue) => {
    this.issues = [...this.issues, sub];
  };

View on GitHub (pinned to 2d90846af9)