---
url: /validation/error-handling.md
description: >-
  Handle ValidationError instances with detailed error information including
  scope, error messages, field paths, and structured ValidationErrorEntry data.
---

When validation fails - on parameters, request payload, or response - KosmoJS throws a `ValidationError`
with detailed information about what went wrong and where.

Your `api/errors.ts` is the central place to handle it. The seeded file gives you a working default;
customize it freely to add logging, change response formats, or handle specific error types differently.
[Details ›](/backend/error-handling.md)

## ValidationError Properties

```ts
export class ValidationError extends Error {
  public target: ValidationTarget;   // which part of the request failed
  public errors: Array<ValidationErrorEntry> = [];
  public errorMessage: string;       // all errors as a single readable string
  public errorSummary: string;       // e.g. "2 validation errors found across 2 fields"
  public route: string;
  public data: unknown;              // the data that failed validation
}

export type ValidationErrorEntry = {
  keyword: string;                   // JSON Schema keyword that triggered the error
  path: string;                      // path to the invalid field
  message: string;                   // human-readable description
  params?: Record<string, unknown>;  // constraint details, e.g. { limit: 5 }
  code?: string;                     // optional code for i18n / custom handling
};
```

The `target` property tells you exactly which part of the request failed:
`"params"`, `"query"`, `"headers"`, `"cookies"`, `"json"`, `"form"`, `"raw"`, or `"response"`.

**Common handling patterns:**

### Error Handling | ValidationError Properties | Hono

```ts
// Hono: api/errors.ts

// All errors as a readable string
if (error instanceof ValidationError) {
  const { target, errorMessage } = error;
  // e.g. "Validation failed: user: missing required properties: "email", "name";
  //       password: must be at least 8 characters long"
  return ctx.json({ error: errorMessage }, 400);
}

// Field-level errors (useful for form responses)
if (error instanceof ValidationError) {
  const messages = error.errors.map(e => `${e.path}: ${e.message}`);
  return ctx.json({ error: "validation_error", target: error.target, messages }, 400);
}

// Log the invalid data, return a summary
if (error instanceof ValidationError) {
  logger.error("Validation failed", { target: error.target, data: error.data });
  return ctx.json({ error: error.errorSummary }, 400);
}
```

### Error Handling | ValidationError Properties | H3

```ts
// H3: api/errors.ts

// All errors as a readable string
if (error instanceof ValidationError) {
  const { target, errorMessage } = error;
  // e.g. "Validation failed: user: missing required properties: "email", "name";
  //       password: must be at least 8 characters long"
  event.res.status = 400;
  return { error: errorMessage };
}

// Field-level errors (useful for form responses)
if (error instanceof ValidationError) {
  const messages = error.errors.map(e => `${e.path}: ${e.message}`);
  event.res.status = 400;
  return { error: "validation_error", target: error.target, messages };
}

// Log the invalid data, return a summary
if (error instanceof ValidationError) {
  logger.error("Validation failed", { target: error.target, data: error.data });
  event.res.status = 400;
  return { error: error.errorSummary };
}
```

### Error Handling | ValidationError Properties | Koa

```ts
// Koa: api/errors.ts

// All errors as a readable string
if (error instanceof ValidationError) {
  const { target, errorMessage } = error;
  // e.g. "Validation failed: user: missing required properties: "email", "name";
  //       password: must be at least 8 characters long"
  ctx.status = 400;
  ctx.body = { error: errorMessage };
}

// Field-level errors (useful for form responses)
if (error instanceof ValidationError) {
  const messages = error.errors.map(e => `${e.path}: ${e.message}`);
  ctx.status = 400;
  ctx.body = { error: "validation_error", target: error.target, messages };
}

// Log the invalid data, return a summary
if (error instanceof ValidationError) {
  logger.error("Validation failed", { target: error.target, data: error.data });
  ctx.status = 400;
  ctx.body = { error: error.errorSummary };
}
```

## Custom Error Messages

The second type argument to your handler also accepts custom error messages per target.
Use `error` as a fallback and `error.fieldName` for field-specific overrides:

```ts [api/users/index.ts]
export default defineRoute<"users">(({ POST }) => [
  POST<
    {
      json: {
        id: number;
        email: string;
        age: number;
      };
    },
    {
      json: {
        error: "Invalid user data provided",
        "error.id": "User ID must be a valid number",
        "error.email": "Please provide a valid email address",
        "error.age": "Age must be a number",
      };
    }
  >(async (ctx) => {
    const { id, email, age } = ctx.validated.json;
  }),
]);
```

Each target has its own message set. For nested fields, use dot notation:

```ts
{
  json: {
    error: "Invalid order data",
    "error.order.items": "Order must contain at least one item",
    "error.order.shipping.address.postalCode": "Invalid postal code format",
  }
}
```

When validation fails, KosmoJS uses the most specific message available -
field-specific first, falling back to the generic `error` if no match is found.
Custom messages appear in the `message` field of each `ValidationErrorEntry`,
so your existing error handler picks them up automatically.
