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
- Guard with `value instanceof ZodError` before calling ZodError.assert(value), and handle the non-Zod branch separately.
- In catch blocks, branch on the error type: `if (e instanceof ZodError) { ... } else { throw e; }` instead of unconditionally asserting.
- 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.
- 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
- Never call ZodError.assert() on a value you haven't already narrowed with instanceof ZodError.
- Prefer safeParse()/safeParseAsync() so parse failures return a result instead of throwing.
- Always branch on error type in catch blocks before reading Zod-specific properties.
- Keep third-party error types in their own catch handlers, separate from Zod parse catch blocks.
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
- Async schemas not supported in object keys currently
- invalid_arguments
- invalid_return_type
- Unmergable intersection. Error path
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)