Standardize error handling so failures remain diagnosable and user-facing messages are complete.
Apply these rules: 1) Preserve diagnostic context when propagating/rethrowing
throw happens in a predictable place, and avoid patterns that always create a fresh error object without considering stack semantics.2) Ensure every issue code is explicitly mapped
ZodIssueCode (or changing which codes are emitted), update defaultErrorMap and/or locale maps so users don’t see generic fallback messages.3) Extract/format messages safely (no unsafe casts)
catch (error) is an Error. Prefer instanceof Error or a shared helper that guarantees a string.4) Don’t unintentionally suppress other validations
Example: safe message extraction
try {
return JSON.parse(val);
} catch (e: unknown) {
const message = e instanceof Error ? e.message : String(e);
ctx.addIssue({
code: ZodIssueCode.invalid_string,
validation: "json",
message,
});
}
Example: stack-preserving handler shape
const zodErrorHandler = (messageOrObject: string | unknown) => {
if (typeof messageOrObject === "string") {
throw ZodError.fromString(messageOrObject);
}
throw messageOrObject; // keep original throw semantics where possible
};
Checklist for PRs in this area
ZodIssueCode has explicit message mapping (default + locales if needed).error as Error without instanceof or a safe conversion helper.Enter the URL of a public GitHub repository