Custom Formatting & Architecture
To truly master zod-validation-error and bend it to complex business requirements (like Internationalization or CLI syntax highlighting), it helps to understand the internal data pipeline.
The Validation Pipeline
When a schema validates data, the error flows through several stages:
- Zod Schema Fails: Zod identifies a validation failure.
- Error Map (The Issue Layer): Zod calls an Error Map. This map looks at the failure code (e.g.,
too_small) and generates a single string message ("Number must be greater than 0"). This string is attached to aZodIssue. - Zod Error: Zod aggregates all
ZodIssueobjects into a singleZodError. - Message Builder (The Aggregation Layer):
zod-validation-errortakes theZodError, loops through the issues, adds paths (at "user.age"), joins them with separators, prepends a prefix, and outputs the final string.
Advanced Scenario: Internationalization (i18n)
Let's say your API serves multiple languages, and you need Zod validation errors to be translated based on an Accept-Language header.
Because translation requires changing the Issue Layer, you must create a Custom Error Map.
import { z as zod } from 'zod';
import { fromZodIssue } from 'zod-validation-error';
// A hypothetical translation function
function translate(code: string, expected: any, locale: string) {
if (locale === 'es') {
if (code === 'invalid_type') return `Se esperaba ${expected}`;
}
return `Expected ${expected}`;
}
// Factory to create a localized error map
const createI18nErrorMap = (locale: string): zod.ZodErrorMap => (issue, ctx) => {
if (issue.code === 'invalid_type') {
return { message: translate(issue.code, issue.expected, locale) };
}
// For issues we haven't explicitly translated, fallback to
// zod-validation-error's beautiful default formatting
const validationError = fromZodIssue({
...issue,
message: issue.message ?? ctx.defaultError,
});
return { message: validationError.message };
};
// Usage in a Request Handler
function handleRequest(req, res) {
const locale = req.headers['accept-language'] || 'en';
const result = mySchema.safeParse(req.body, {
error: createI18nErrorMap(locale)
});
// ...
}
Advanced Scenario: CLI Colorization
If you are building a CLI tool, you might want to print validation errors in Red, and highlight the object paths in Yellow. Because this requires changing how issues are Aggregated, you write a custom MessageBuilder.
import { z as zod } from 'zod';
import { type MessageBuilder, fromError } from 'zod-validation-error';
import chalk from 'chalk';
// Create custom MessageBuilder
const cliMessageBuilder: MessageBuilder = (issues) => {
return issues
.map((issue) => {
// Colorize the path in yellow, and the message in red
const path = issue.path.join('.');
return `${chalk.yellow(path)}: ${chalk.red(issue.message)}`;
})
.join('\n'); // Join with newlines for terminal readability
};
// Usage
try {
configSchema.parse(userConfig);
} catch (err) {
const validationError = fromError(err, {
messageBuilder: cliMessageBuilder,
});
console.log(validationError.toString());
process.exit(1);
}