H3Event

H3Event, carries incoming request, prepared response and context.

With each HTTP request, H3 internally creates an H3Event object and passes it though event handlers until sending the response.

Read more in Request Lifecycle.

An event is passed through all the lifecycle hooks and composable utils to use it as context.

Example:

app.get("/", async (event) => {
  // Log HTTP request
  console.log(`[${event.req.method}] ${event.req.url}`);

  // Parsed URL and query params
  const searchParams = event.url.searchParams;

  // Try to read request JSON body
  const jsonBody = await event.req.json().catch(() => {});

  return "OK";
});

H3Event Methods

H3Event.waitUntil

Tell the runtime about an ongoing operation that shouldn't close until the promise resolves.

import { logRequest } from "./tracing.mjs";

app.get("/", (event) => {
  request.waitUntil(logRequest(request));
  return "OK";
});
To release per-request resources (timers, upstream connections, file handles) once the event is fully over — on every runtime — use the onDispose(event, cb) utility.

H3Event Properties

H3Event.app?

Access to the H3 application instance.

H3Event.context

The context is an object that contains arbitrary information about the request.

You can store your custom properties inside event.context to share across utils.

Known context keys:

  • context.params: Matched router parameters.
  • middlewareParams: Matched middleware parameters
  • matchedRoute: Matched router route object.
  • sessions: Cached session data.
  • basicAuth: Basic authentication data.

H3Event.req

Incoming HTTP request info based on native Web Request with additional runtime addons (see srvx docs).

app.get("/", async (event) => {
  const url = event.req.url;
  const method = event.req.method;
  const headers = event.req.headers;

  // (note: you can consume body only once with either of this)
  const bodyStream = await event.req.body;
  const textBody = await event.req.text();
  const jsonBody = await event.req.json();
  const formDataBody = await event.req.formData();

  return "OK";
});

H3Event.url

Access to the full parsed request URL.

app.get("/", (event) => {
  const { pathname, search, searchParams } = event.url;

  return "OK";
});

Pathname decoding

event.url.pathname is percent-decoded once, while event.req.url keeps the original encoding exactly as it arrived on the wire. H3 decodes eagerly so that route matching and any pathname-based middleware always compare the same normalized value — otherwise a request to /%61dmin would slip past an /admin guard and still reach the /admin route.

Decoding is a single decodeURI pass, and the result is re-serialized by the URL parser. This means only unreserved escapes visibly decode:

Requestevent.url.pathnameWhy
/%41/AUnreserved escape, decodes
/a%2eb/a.bUnreserved escape, decodes
/a/%2e%2e/b/bDecoded .. collapses during re-parse
/x%2fy/x%2fyStructural, kept encoded
/100%25/100%25Structural, kept encoded
/a%20b/a%20bRe-encoded by the URL serializer
/caf%C3%A9/caf%C3%A9Re-encoded by the URL serializer

Either way, a route param can never contain a path separator that the router did not match on: %2f stays encoded, and %5c decodes to \, which the URL parser then normalizes into a real / the router splits on (/a%5cb matches the route /a/:id).

Never decode event.url.pathname again. A second decodeURIComponent can reintroduce a / or .. that routing and middleware never saw, which is a path traversal vector when the value reaches a filesystem or an upstream URL. To read a route param in decoded form, use getRouterParams(event, { decode: true }), which decodes everything else but keeps encoded separators encoded.

Requests with malformed percent-encoding (such as /foo% or /%ZZ) are rejected with a 400 Bad Request before any handler runs. Set the allowMalformedURL app option to receive the raw pathname instead.

H3Event.res

Prepared HTTP response status and headers.

app.get("/", (event) => {
  event.res.status = 200;
  event.res.statusText = "OK";
  event.res.headers.set("x-test", "works");

  return "OK";
});
Read more in Preparing Response.