Zod Error Handling — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Zod Error Handling (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
.parse() vs .safeParse(): The Decision Rule| Use | When |
|---|---|
.parse() | Data is expected to always be valid; failure is a programming error or unrecoverable exception. Use with try/catch. |
.safeParse() | Validating untrusted external data where failures are routine (forms, API requests). Returns result without throwing. |
.parseAsync() / .safeParseAsync() | Schemas with async .refine() calls. |
// .parse() — throws ZodError on failure
try {
const user = UserSchema.parse(rawData);
// user is fully typed
} catch (err) {
if (err instanceof ZodError) {
// handle validation failure
}
}
// .safeParse() — never throws; returns discriminated union
const result = UserSchema.safeParse(rawData);
if (result.success) {
const user = result.data; // typed T
} else {
const errors = result.error; // ZodError
}Prefer `.safeParse()` in performance-critical code. Throwing and catching exceptions in JavaScript is expensive. In high-throughput handlers or tight loops, .safeParse() avoids that overhead entirely.
A ZodError contains an .issues array. Each issue provides:
{
code: "invalid_type" | "too_small" | "custom" | ..., // Machine-readable error code
path: ["user", "address", 0, "zip"], // Path to invalid field
message: "Expected string, received number", // Human-readable message
// Additional fields depending on code
}Access issues directly for programmatic error handling:
const result = Schema.safeParse(data);
if (!result.success) {
result.error.issues.forEach(issue => {
console.log(`${issue.path.join(".")} — ${issue.message}`);
});
}Zod 4 replaces the deprecated .format() and .flatten() instance methods with top-level functions. Choose based on audience:
z.prettifyError(error) — Developer LogsGenerates a multi-line, human-readable string. Use for server logs and debug output:
const result = Schema.safeParse(badData);
if (!result.success) {
console.error(z.prettifyError(result.error));
// ✗ user.email: Invalid email
// ✗ user.age: Expected number, received string
}z.flattenError(error) — Simple API ResponsesReturns a shallow object: { formErrors: string[], fieldErrors: { [key]: string[] } }. Best for flat schemas (one level deep):
const result = CreateUserSchema.safeParse(body);
if (!result.success) {
return res.status(400).json(z.flattenError(result.error));
// { formErrors: [], fieldErrors: { email: ["Invalid email"], age: ["Required"] } }
}z.treeifyError(error) — Nested UI FormsConverts a flat ZodError into a nested object mirroring the schema shape. Essential for complex multi-level forms:
const result = OrderSchema.safeParse(formData);
if (!result.success) {
const tree = z.treeifyError(result.error);
// tree.properties.shipping.properties.address.properties.zip.errors
// → ["Invalid ZIP code format"]
}TypeScript hint: If the IDE shows properties doesn't exist, pass the schema type explicitly:
const tree = z.treeifyError<typeof OrderSchema>(result.error); // ✅Returning the raw ZodError or unformatted .issues array to API clients is an anti-pattern:
// BAD: Leaks internal structure
res.status(400).json(result.error);
// GOOD: Formatted for client consumption
res.status(400).json({
message: "Validation failed",
errors: z.flattenError(result.error).fieldErrors,
});The fragmented Zod 3 approach (invalid_type_error, required_error, per-schema errorMap) is replaced by a single unified `error` parameter on all schema methods:
// Zod 3 (deprecated pattern)
z.string({ invalid_type_error: "Must be a string", required_error: "Required" })
// Zod 4 — unified error parameter
z.string({ error: "Must be a string" })
z.string({ error: (issue) => issue.input === undefined ? "Required" : "Must be text" })The error parameter accepts:
(issue: ZodIssue) => string | undefined (for conditional messages).parse({ errorMap: ... }) (rare)z.config({ customError: ... })// Schema-level wins over global
z.config({
customError: (issue) => "Global default error",
});
const EmailSchema = z.email({ error: "Must be a valid email address" }); // This winsSet global error handling at application startup:
import { z } from "zod";
z.config({
customError: (issue, ctx) => {
if (issue.code === "invalid_type" && issue.input === undefined) {
return "This field is required";
}
return undefined; // Fall through to Zod's default message
},
});Load locale files for translated error messages:
import { z } from "zod";
import { es } from "zod/locales/es"; // Spanish
z.config(es());reportInput Security WarningZod intentionally omits raw input from error issues to prevent accidental logging of passwords or PII. The reportInput: true flag re-enables this:
// RISKY: Logs user's raw password in error output
z.config({ reportInput: true });
// Only enable in development/debugging environments
// Never in production~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.