
# Query Parameters

By default **no query parameter is part of the cache key**, and none reaches the handler: `/swatch`, `?color=red`, and `?color=red&utm=x` all share one entry. Keying on the full query is what shatters a cache into countless entries — tracking tags and cache busters alone can destroy the hit rate — so query parameters are opt-in.

`allowQuery` is that opt-in, as an allowlist. **Only the listed parameters** vary the key and reach the handler; everything else is ignored:

```ts
const handler = defineCachedHandler(
  async (event) => {
    const url = event.url ?? new URL(event.req.url);
    const color = url.searchParams.get("color") ?? "default";
    return new Response(renderSwatch(color), {
      headers: { "content-type": "text/html" },
    });
  },
  {
    maxAge: 300,
    allowQuery: ["color"],
  },
);

// All of these share ONE cached entry (only `color` matters):
await handler({ req: new Request("https://shop.dev/swatch?color=red") });
await handler({ req: new Request("https://shop.dev/swatch?color=red&lang=en") });
await handler({ req: new Request("https://shop.dev/swatch?color=red&utm=ig") });
```

Matching is normalized and order-independent: `?color=red&size=l` and `?size=l&color=red` share an entry, and repeated array parameters such as `?color=red&color=blue` match in any order.

> [!TIP]
> **Ignored parameters never reach the handler.** Parameters outside the allowlist are stripped from the URL the handler receives — just like [undeclared cookies](/docs/cookies) and [undeclared headers](/docs/handler#headers-the-handler-cant-see). A handler therefore cannot accidentally render output from a parameter the key does not cover.

> [!IMPORTANT]
> A handler that reads `url.searchParams` without `allowQuery` sees an empty query. Declare every parameter the output depends on.

`allowQuery` adds nothing to the response `Vary` header: query parameters live in the URL, and downstream caches already key on the URL.

## Keying the full query

`allowQuery: true` puts the **whole** query string in the key and passes it to the handler unchanged:

```ts
defineCachedHandler(myHandler, {
  maxAge: 300,
  allowQuery: true,
});
```

Use it for a route whose parameters are not known ahead of time, such as a proxy or a search endpoint. Every distinct query becomes its own entry, including tracking parameters, so prefer an allowlist where the parameter names are known.

## Edge cases

- **`allowQuery: []` and `allowQuery: false` are the default.** Both remove the whole query string, as an unset option does. Every request to the path shares one entry, and the handler always receives a query-less URL.
- **Names are case-sensitive.** `allowQuery: ["color"]` does not match `?Color=red`.
- **`getKey` overrides the key, not the filtering.** A custom `getKey` decides the entire cache key, and `allowQuery` no longer affects it — but disallowed parameters are **still stripped** from the URL the handler receives:

  ```ts
  defineCachedHandler(myHandler, {
    maxAge: 300,
    allowQuery: ["color"], // still strips other params from the handler's URL
    getKey: (event) => new URL(event.req.url).pathname, // but this defines the key
  });
  ```
