Error Handling

Send errors by throwing an HTTPError.

H3 captures all possible errors during request lifecycle.

HTTPError

You can create and throw HTTP errors using HTTPError with different syntaxes.

import { HTTPError } from "h3";

app.get("/error", (event) => {
  // Using message and details
  throw new HTTPError("Invalid user input", { status: 400 });

  // Using HTTPError.status(code)
  throw HTTPError.status(400, "Bad Request");

  // Using single pbject
  throw new HTTPError({
    status: 400,
    statusText: "Bad Request",
    message: "Invalid user input",
    data: { field: "email" },
    body: { date: new Date().toJSON() },
    headers: {},
  });
});

This will end the request with 400 - Bad Request status code and the following JSON response:

{
  "date": "2025-06-05T04:20:00.0Z",
  "status": 400,
  "statusText": "Bad Request",
  "message": "Invalid user input",
  "data": {
    "field": "email"
  }
}

HTTPError Fields

  • status: HTTP status code in the range 200–599.
  • statusText: HTTP status text to be sent in the response header.
  • message: Error message to be included in the JSON body.
  • data: Additional data to be attached under the data key in the error JSON body.
  • body: Additional top-level properties to be attached in the error JSON body.
  • headers: Additional HTTP headers to be sent in the error response.
  • cause: The original error object that caused this error, useful for tracing and debugging.
  • unhandled: Indicates whether the error was thrown for unknown reasons. See Unhandled Errors.
Error statusText should be short (max 512 to 1024 characters) and only include tab, spaces or visible ASCII characters and extended characters (byte value 128–255). Prefer message in JSON body for extended message.

Unhandled Errors

Any error that occurs during calling request lifecycle without using HTTPError will be processed as an unhandled error.

app.get("/error", (event) => {
  // This will cause an unhandled error.
  throw new Error("Something went wrong");
});
For enhanced security, H3 hides certain fields of unhandled errors (data, body, stack and message) in JSON response.

Catching Errors

Using global onError hook:

import { H3, onError } from "h3";

// Globally handling errors
const app = new H3({
  onError: (error) => {
    console.error(error);
  },
});

Using onError middleware to catch errors.

import { onError } from "h3";

// Handling errors using middleware
app.use(
  onError(event, (event, error) => {
    console.error(error);
  }),
);
When using nested apps, global hooks of sub-apps will not be called. Therefore it is better to use onError middleware.