Request

H3 request utilities.

Body

assertBodySize(event, limit)

Asserts that request body size is within the specified limit.

If body size exceeds the limit, throws a 413 Request Entity Too Large response error.

Example:

app.get("/", async (event) => {
  await assertBodySize(event, 10 * 1024 * 1024); // 10MB
  const data = await event.req.formData();
});

readBody(event, options?)

Reads request body and tries to parse using JSON.parse or URLSearchParams.

By default the body is parsed as JSON (falling back to URL-encoded parsing when the Content-Type is application/x-www-form-urlencoded). Other body types, such as multipart/form-data, must be opted into explicitly via options.type and are never auto-detected from the request headers.

Example:

app.post("/", async (event) => {
  const body = await readBody(event);
});

Example:

app.post("/upload", async (event) => {
  const body = await readBody(event, { type: "formData" });
});

readValidatedBody(event, validate)

Tries to read the request body via readBody, then uses the provided validation schema or function and either throws a validation error or returns the result.

You can use a simple function to validate the body or use a Standard-Schema compatible library like zod to define a schema.

Example:

function validateBody(body: any) {
  return typeof body === "object" && body !== null;
}
app.post("/", async (event) => {
  const body = await readValidatedBody(event, validateBody);
});

Example:

import { z } from "zod";
const objectSchema = z.object({
  name: z.string().min(3).max(20),
  age: z.number({ coerce: true }).positive().int(),
});
app.post("/", async (event) => {
  const body = await readValidatedBody(event, objectSchema);
});

Example:

import * as v from "valibot";
app.post("/", async (event) => {
  const body = await readValidatedBody(
    event,
    v.object({
      name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
      age: v.pipe(v.number(), v.integer(), v.minValue(1)),
    }),
    {
      onError: ({ issues }) => ({
        statusText: "Custom validation error",
        message: v.summarize(issues),
      }),
    },
  );
});

Query (HTTP QUERY method)

Utilities for the HTTP QUERY method (RFC 10008): advertise the query formats a resource accepts and validate the request Content-Type.

appendAcceptQuery(event, mediaTypes)

Advertise the query formats a resource accepts by setting the Accept-Query response header (RFC 10008, HTTP QUERY method).

The media types are serialized as a Structured Fields List: the base media type becomes a token and any ;name=value parameters are emitted with their values as quoted strings.

Example:

app.query("/search", (event) => {
  appendAcceptQuery(event, ["application/sql;charset=UTF-8", "application/jsonpath"]);
  // Accept-Query: application/sql;charset="UTF-8", application/jsonpath
  return handleSearch(event);
});

requireContentType(event, acceptedTypes)

Assert that the request Content-Type is present and one of the accepted media types, following the requirements of RFC 10008 for the HTTP QUERY method.

Throws:

  • 400 Bad Request if the Content-Type header is missing.
  • 422 Unprocessable Content if the Content-Type header is malformed.
  • 415 Unsupported Media Type if the media type is not accepted.

Accepted types may use wildcards: * / */* match anything and type/* matches any subtype of type.

Example:

app.query("/search", async (event) => {
  requireContentType(event, ["application/sql", "application/jsonpath"]);
  const body = await readBody(event, { type: "text" });
  // ...
});

Cache

handleCacheHeaders(event, opts)

Check request caching headers (If-None-Match, If-Modified-Since) and add caching headers (Last-Modified, ETag, Cache-Control).

Note: public is added by default, but never alongside a caller-supplied private/no-store directive, so passing cacheControls: ["private"] no longer produces a contradictory public, private.

More Request Utils

assertMethod(event, expected, allowHead?)

Asserts that the incoming request method is of the expected type using isMethod.

If the method is not allowed, it will throw a 405 error and include an Allow response header listing the permitted methods, as required by RFC 9110.

If allowHead is true, it will allow HEAD requests to pass if the expected method is GET.

Example:

app.get("/", (event) => {
  assertMethod(event, "GET");
  // Handle GET request, otherwise throw 405 error
});

getQuery(event)

Get parsed query string object from the request URL.

Example:

app.get("/", (event) => {
  const query = getQuery(event); // { key: "value", key2: ["value1", "value2"] }
});

getRequestHost(event, opts: { xForwardedHost? })

Get the request hostname.

If xForwardedHost is true, it will use the x-forwarded-host header if it exists.

If no host header is found, it will return an empty string.

Security: The returned host reflects the client-supplied Host (or X-Forwarded-Host) header and can be spoofed. Do not trust it for security decisions (CSRF/origin checks, cache keys, generating absolute links sent to other users) unless the Host value is pinned or validated upstream (e.g. an allow-list of expected hosts, or a reverse proxy that overwrites it).

Example:

app.get("/", (event) => {
  const host = getRequestHost(event); // "example.com"
});

getRequestIP(event)

Try to get the client IP address from the incoming request.

If xForwardedFor is true, it will use the x-forwarded-for header if it exists.

If IP cannot be determined, it will default to undefined.

Example:

app.get("/", (event) => {
  const ip = getRequestIP(event); // "192.0.2.0"
});

getRequestProtocol(event, opts: { xForwardedProto? })

Get the request protocol.

If xForwardedProto is true, it will use the x-forwarded-proto header if it exists. When the header contains a comma-separated list of protocols, the first entry is used.

Note: This header is opt-in (default false) since it can be spoofed by clients. Only enable it when your application runs behind a trusted reverse proxy or CDN that sets this header. This default was changed to match getRequestHost (xForwardedHost) and getRequestIP (xForwardedFor).

If protocol cannot be determined, it will default to "http".

Example:

app.get("/", (event) => {
  const protocol = getRequestProtocol(event); // "https"
});

getRequestURL(event, opts: { xForwardedHost?, xForwardedProto? })

Generated the full incoming request URL.

If xForwardedHost is true, it will use the x-forwarded-host header if it exists.

If xForwardedProto is true, it will use the x-forwarded-proto header if it exists.

Security: The .origin and .host of the returned URL are derived from the client-supplied Host (or X-Forwarded-Host) header and can be spoofed. Do not trust them for security decisions (CSRF/origin checks, cache keys, generating absolute links sent to other users) unless the Host value is pinned or validated upstream (e.g. an allow-list of expected hosts, or a reverse proxy that overwrites it). The .pathname and .search are not derived from the spoofable host, but remain untrusted client input — validate or encode them for their eventual sink (e.g. filesystem lookups, HTML output, downstream queries).

Example:

app.get("/", (event) => {
  const url = getRequestURL(event); // "https://example.com/path"
});

getRouterParam(event, name, opts: { decode? })

Get a matched route param by name.

If decode option is true, it will decode the matched route param (like decodeURIComponent), except encoded path separators (%2f, %5c) are kept encoded so decoding can never reintroduce a / or \ the router never matched.

Example:

app.get("/", (event) => {
  const param = getRouterParam(event, "key");
});

getRouterParams(event, opts: { decode? })

Get matched route params.

If decode option is true, it will decode the matched route params (like decodeURIComponent), except encoded path separators (%2f, %5c) are kept encoded so decoding can never reintroduce a / or \ the router never matched.

Example:

app.get("/", (event) => {
  const params = getRouterParams(event); // { key: "value" }
});

getValidatedQuery(event, validate)

Get the query param from the request URL validated with validate function.

You can use a simple function to validate the query object or use a Standard-Schema compatible library like zod to define a schema.

Example:

app.get("/", async (event) => {
  const query = await getValidatedQuery(event, (data) => {
    return "key" in data && typeof data.key === "string";
  });
});

Example:

import { z } from "zod";
app.get("/", async (event) => {
  const query = await getValidatedQuery(
    event,
    z.object({
      key: z.string(),
    }),
  );
});

Example:

import * as v from "valibot";
app.get("/", async (event) => {
  const params = await getValidatedQuery(
    event,
    v.object({
      key: v.string(),
    }),
    {
      onError: ({ issues }) => ({
        statusText: "Custom validation error",
        message: v.summarize(issues),
      }),
    },
  );
});

getValidatedRouterParams(event, validate)

Get matched route params and validate with validate function.

If decode option is true, it will decode the matched route params (like decodeURIComponent), except encoded path separators (%2f, %5c) are kept encoded so decoding can never reintroduce a / or \ the router never matched.

You can use a simple function to validate the params object or use a Standard-Schema compatible library like zod to define a schema.

Example:

app.get("/:key", async (event) => {
  const params = await getValidatedRouterParams(event, (data) => {
    return "key" in data && typeof data.key === "string";
  });
});

Example:

import { z } from "zod";
app.get("/:key", async (event) => {
  const params = await getValidatedRouterParams(
    event,
    z.object({
      key: z.string(),
    }),
  );
});

Example:

import * as v from "valibot";
app.get("/:key", async (event) => {
  const params = await getValidatedRouterParams(
    event,
    v.object({
      key: v.pipe(v.string(), v.picklist(["route-1", "route-2", "route-3"])),
    }),
    {
      decode: true,
      onError: ({ issues }) => ({
        statusText: "Custom validation error",
        message: v.summarize(issues),
      }),
    },
  );
});

isMethod(event, expected, allowHead?)

Checks if the incoming request method is of the expected type.

If allowHead is true, it will allow HEAD requests to pass if the expected method is GET.

Example:

app.get("/", (event) => {
  if (isMethod(event, "GET")) {
    // Handle GET request
  } else if (isMethod(event, ["POST", "PUT"])) {
    // Handle POST or PUT request
  }
});

requestWithBaseURL(req, base)

Create a lightweight request proxy with the base path stripped from the URL pathname.

requestWithURL(req, url)

Create a lightweight request proxy that overrides only the URL.

Avoids cloning the original request (no new Request() allocation).

toRequest(input, options?)

Convert input into a web Request.

If input is a relative URL, it will be normalized into a full path based on headers.

If input is already a Request and no options are provided, it will be returned as-is.

getRequestFingerprint(event, opts)

Get a unique fingerprint for the incoming request.