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:

  1. Zod Schema Fails: Zod identifies a validation failure.
  2. 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 a ZodIssue.
  3. Zod Error: Zod aggregates all ZodIssue objects into a single ZodError.
  4. Message Builder (The Aggregation Layer): zod-validation-error takes the ZodError, 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);
}