# The HTTP QUERY Method: A Complete Developer Guide

> The IETF published RFC 10008 in June 2026, standardizing a new HTTP method called QUERY. It sits between GET (safe, idempotent, cacheable, no body) and POST (supports a body, but not safe or idempotent). QUERY is safe, idempotent, cacheable, and accepts a request body. It's the most significant addition to HTTP semantics since PATCH in 2010.

Use it when you need to send complex filter payloads, large structured queries, or any read-only request that doesn't fit cleanly in a URL.

Hold off when your infrastructure hasn't been tested for support yet. That means CDNs, proxies, load balancers, and API gateways.

`GET`, `POST`, `PUT`, `DELETE`, `PATCH`. Most developers know these without thinking. What's always been missing is a method built for this specific scenario: you want to query a resource using a structured body, and you're not changing anything on the server.

That gap is now closed.

RFC 10008 defines QUERY: a safe, idempotent, cacheable HTTP method that accepts a request body. It doesn't replace anything you're already using. It gives you a precise tool for a job that developers have been handling with workarounds for years.

This guide covers the history, the technical spec, how QUERY compares to `GET` and `POST`, practical code examples, known limitations, and where adoption stands today.

## A Brief History: The First New HTTP Method Since PATCH (2010)

`PATCH` was standardized in RFC 5789 in March 2010, making it the last widely adopted addition to the HTTP method registry before `QUERY`. That's a sixteen-year gap.

`QUERY` started life as `SEARCH` in early drafts, drawing on WebDAV conventions. The HTTP Working Group eventually settled on `QUERY` because the name maps directly to the URI query component and describes a generic read-only operation rather than a specific search use case. Alternatives like `SEARCH`, `PROPFIND`, and `REPORT` all came from the WebDAV world and carried narrower connotations.

The final RFC was authored by Julian Reschke (greenbytes GmbH), James M. Snell (Cloudflare), and Mike Bishop (Akamai), and published as a Proposed Standard by the IETF HTTP Working Group in June 2026.

That sixteen-year gap matters. HTTP methods don't get added casually. A new method touches servers, frameworks, proxies, caches, browsers, gateways, observability tools, and API design conventions. The working group deciding this problem needed a protocol-level fix tells you something about how persistent and widespread the pain was.

## Why This Is a Major Change

Most HTTP methods are straightforward. `QUERY` addresses a subtler problem: the semantic mismatch that's existed at the protocol level whenever developers need to express a complex, read-only operation.

Without it, you have two options and neither is right. `GET` is semantically correct but structurally limited. `POST` fits structurally but signals the wrong intent to every layer of your infrastructure, including your CDN, cache, retry logic, and clients.

That mismatch leaks everywhere. Caching gets worse. Automatic retries become risky. Logs get noisier. API semantics get harder to understand. Documentation has to explain things the protocol should have expressed itself.

`QUERY` doesn't introduce a new application pattern. It standardizes one developers were already using in disguised form.

## How Developers Have Been Handling Queries Without QUERY

### Using GET with Query Parameters

`GET` is the natural choice for read-only operations. Query parameters sit in the URL, show up in browser history, appear in access logs, and are easy to share and bookmark.

```bash
GET /users?role=admin&sort=name&page=1&include[]=email&include[]=created_at HTTP/1.1
Host: api.example.com
```

This works fine for simple filters. It breaks down fast when queries grow complex.

### Using POST as a Workaround

When query parameters become too large or too structured for a URL, developers reach for POST:
```bash
POST /users/search HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "role": "admin",
  "filters": {
    "created_after": "2024-01-01",
    "tags": ["enterprise", "verified"]
  },
  "sort": { "field": "name", "direction": "asc" },
  "pagination": { "page": 1, "per_page": 50 }
}
```

GraphQL is the most prominent example. GraphQL queries are inherently read-only, but they're routinely sent as `POST` requests because they exceed URL length limits and need a structured body.

The workaround functions. It just carries real costs.

## The Problems with Existing Approaches

### URL Size Limits

URLs have a practical safe limit of around 2,000 characters, though implementations vary. Some proxies enforce limits as low as 4,096 bytes; some servers reject requests above 8,192. Complex filter objects, nested JSON structures, or multi-field sort configs hit these limits quickly.

Even before you hit hard limits, very long URLs are brittle. They're hard to inspect, hard to debug, and awkward in logs, devtools, and tracing systems.

### Security and Logging Exposure

Request URIs get logged by default in virtually every web server, proxy, and CDN access log. Query parameters, including filters that may carry sensitive data, appear in those logs. Request bodies aren't logged by default. Moving query data into a body meaningfully reduces the exposure of credentials or sensitive filter values through log aggregation pipelines.

### Non-Idempotent POST Semantics

`POST` signals that the operation may modify server state. That single fact ripples through your entire infrastructure:

- **Caches** refuse to store POST responses without explicit headers.

- **Retry** logic in HTTP clients won't automatically repeat a failed POST.

- **Browsers** show "Confirm form resubmission" dialogs when users refresh a POST-loaded page.

- **CDNs** can't safely serve a cached POST response.

You can document that your `POST /search` endpoint is read-only. The problem is the protocol still says POST.

### Caching Breaks Down

RFC 9110 permits caching POST responses only when the response includes explicit freshness information and a `Content-Location` header matching the request URI. In practice, most infrastructure treats `POST` as uncacheable. Search-heavy applications pay the performance cost of re-executing identical queries on every request.

### Semantic Confusion

`POST /users/search` is an API convention, not a protocol statement. It requires out-of-band documentation to communicate that the operation is safe. Consumers need to read your API docs, or inspect response headers, to know whether retrying that `POST` is okay. `QUERY` encodes that information in the method name itself.

## What Is the HTTP QUERY Method?

### Definition (RFC 10008)

Per RFC 10008:

> "A QUERY requests that the request target process the enclosed content in a safe and idempotent manner, returning a representation of the results of processing."


`QUERY` is an IANA-registered HTTP method in the same registry as `GET`, `POST`, `PUT`, `DELETE`, and `PATCH`.

| **Property**     | **Value**                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------- |
| **Safe**         | Yes. `QUERY` must not modify server state.                                                   |
| **Idempotent**   | Yes. Repeating the same request produces the same result.                                    |
| **Cacheable**    | Yes. Responses may be cached using the request body as part of the cache key.                |
| **Request body** | Expected. The body defines the query.                                                        |
| **Content-Type** | **Mandatory**. Servers must reject requests with missing or inconsistent media type information. |

## How QUERY Solves These Problems

The short version: `QUERY` takes body support from `POST` and the safe semantics of `GET`.

Large or structured filters no longer have to be crammed into the URL. Sensitive query inputs are less likely to end up in standard URI logs. Clients and intermediaries can understand that the request is safe to repeat. Caching becomes possible without pretending a read operation is a `POST`.

API design gets cleaner too. Instead of inventing endpoint patterns like `/search`, `/filter`, or `/lookup` just to justify a POST, you can keep a resource-oriented URL and express the operation with the method itself.

Compare these two:
```bash
POST /products/search
```

versus:
```bash
QUERY /products
```

The second says more with less.

## The Accept-Query Header

RFC 10008 introduces the `Accept-Query` response header. Servers use it to advertise which query media types they support:
```
Accept-Query: application/x-www-form-urlencoded, application/json, application/sql
```

`QUERY` is intentionally flexible about body format. The method defines the semantics of the operation, not the query language. One API might accept JSON. Another might accept SQL. Another might support JSONPath, XSLT, or something domain-specific. `Accept-Query` lets clients know what's available.

A client can discover support with `OPTIONS` or `HEAD` before committing to a full request:
```bash
OPTIONS /reports HTTP/1.1
Host: api.example.com


HTTP/1.1 200 OK
Allow: GET, HEAD, QUERY, OPTIONS
Accept-Query: application/json, application/sql
```

This kind of discoverability matters when building general-purpose clients, SDKs, gateways, or integrations that need to negotiate behavior without hardcoded assumptions.

## Code Examples

### Basic QUERY Request (Raw HTTP)

```bash
QUERY /users HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "role": "admin",
  "sort": "name",
  "page": 1
}
```

Same target resource. Same mental model as a read request. The query goes in the body instead of the URL.

### QUERY with a JSON Filter Body

```json
QUERY /products HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "filters": {
    "category": "electronics",
    "price": { "min": 100, "max": 500 },
    "in_stock": true,
    "tags": ["wireless", "bluetooth"]
  },
  "sort": { "field": "price", "direction": "asc" },
  "pagination": { "page": 2, "per_page": 20 }
}
```

This is the kind of request that becomes painful as a URL but feels completely natural as a body.

### QUERY with an SQL Body

RFC 10008 doesn't prescribe a body format. SQL is a valid content type when client and server agree:
```bash
QUERY /data/analytics HTTP/1.1
Host: api.example.com
Content-Type: application/sql

SELECT region, SUM(revenue) AS total
FROM sales
WHERE year = 2025
GROUP BY region
ORDER BY total DESC
LIMIT 10
```

You probably wouldn't expose raw SQL on a public API, but for internal systems, controlled environments, or domain-specific data services, this pattern works well.

### QUERY with a JSONPath Body

```bash
QUERY /documents HTTP/1.1
Host: api.example.com
Content-Type: application/jsonpath

$.store.book[?(@.price < 10)].title
```

The point isn't that `QUERY` invents a query language. It gives existing query languages a method with the right HTTP semantics.

### Implementing QUERY Support in Node.js / Express

Express doesn't ship with a built-in router method for QUERY. Use `app.all()` and filter by `req.method`:
```javascript
const express = require('express');
const app = express();

app.use(express.json());

// Handle QUERY requests on /users
app.all('/users', (req, res, next) => {
  if (req.method !== 'QUERY') return next();

  const contentType = req.headers['content-type'];
  if (!contentType || !contentType.includes('application/json')) {
    return res.status(415).json({ error: 'Unsupported Media Type. Expected application/json.' });
  }

  const { role, sort, page = 1 } = req.body;

  if (!role) {
    return res.status(400).json({ error: 'Missing required field: role' });
  }

  const results = queryDatabase({ role, sort, page });

  res.status(200)
    .set('Content-Location', `/users/query-results/${generateResultId()}`)
    .json(results);
});

// Advertise QUERY support via OPTIONS
app.options('/users', (req, res) => {
  res.set({
    'Allow': 'GET, HEAD, QUERY, OPTIONS',
    'Accept-Query': 'application/json'
  }).sendStatus(204);
});

app.listen(3000, () => console.log('Server listening on port 3000'));
```

A few practical notes:

- Check `req.method === 'QUERY'` explicitly. Express has no app.query() shorthand.

- Validate `Content-Type` and return `415` if it's absent or unrecognized.

- Return `400` for missing or malformed query fields.

- Set `Content-Location` on the response to point to a cacheable result URI.

- Expose `Accept-Query` in your `OPTIONS` handler so clients can discover support.

Most of the work here isn't routing. It's making sure the rest of your stack passes the method and body through correctly.

## Sending a QUERY Request with fetch (JavaScript)

`QUERY` isn't a forbidden method in the Fetch spec and won't be normalized away. fetch() sends it as-is:
```javascript
async function queryUsers(filters) {
  const response = await fetch('https://api.example.com/users', {
    method: 'QUERY',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(filters)
  });

  if (!response.ok) {
    throw new Error(`Query failed: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

const users = await queryUsers({
  role: 'admin',
  sort: 'name',
  page: 1
});
```

As of mid-2026, fetch() sends QUERY requests without issue. Browser-native caching of QUERY responses isn't implemented yet in Chrome or Firefox, so the HTTP cache won't behave the same way it does for GET until browsers add support.

### Checking QUERY Support via OPTIONS

Before sending a `QUERY` request to an unknown server, probe for support first:
```javascript
async function supportsQuery(endpoint) {
  const response = await fetch(endpoint, { method: 'OPTIONS' });
  const allow = response.headers.get('Allow') || '';
  const acceptQuery = response.headers.get('Accept-Query');

  return {
    supportsQuery: allow.toUpperCase().includes('QUERY'),
    acceptedTypes: acceptQuery ? acceptQuery.split(',').map(t => t.trim()) : []
  };
}

const { supportsQuery, acceptedTypes } = await supportsQuery('https://api.example.com/users');

if (supportsQuery) {
  console.log('Server accepts QUERY with types:', acceptedTypes);
} else {
  console.log('Server does not support QUERY. Falling back to POST.');
}
```

This is especially useful during gradual rollouts, when some environments support `QUERY` and others still rely on legacy patterns.

## GET vs POST vs QUERY

![Http Get vs Post vs Query method](https://assets.xorblin.com/blog-images/GET-vs-POST-vs-QUERY-Compare.webp)

| **Property** | **GET** | **POST** | **QUERY** |
|--------------|---------|----------|-----------|
| **Safe** | ✅ Yes | ❌ No | ✅ Yes |
| **Idempotent** | ✅ Yes | ❌ No | ✅ Yes |
| **Cacheable** | ✅ Yes | ⚠️ Limited | ✅ Yes (body-keyed) |
| **Request body** | ❌ Undefined semantics | ✅ Yes | ✅ Yes (expected) |
| **URL length constraint** | ❌ Yes (~2,000 chars safe) | ✅ No | ✅ No |
| **Automatic retry safe** | ✅ Yes | ❌ No | ✅ Yes |
| **Browser form support** | ✅ Yes | ✅ Yes | ⚠️ Proposal pending |
| **CORS safelisted** | ✅ Simple requests (some) | ⚠️ Depends on content type | ❌ Preflight required |
| **Sensitive data in logs** | ❌ URL is logged | ✅ Body not logged | ✅ Body not logged |
| **Signals read-only intent** | ✅ Yes | ❌ No | ✅ Yes |
| **RFC** | RFC 9110 | RFC 9110 | RFC 10008 (June 2026) |

## Gotchas and Limitations

This is the part most enthusiastic blog posts skip. Read it before touching production.

### Limited Infrastructure Support as of 2026

The spec is published. Widespread infrastructure support isn't there yet. Neither Chrome nor Firefox caches repeated identical `QUERY` responses natively as of mid-2026. Proxies, CDNs, and load balancers that strip or reject bodies from unfamiliar methods will silently break `QUERY` requests.

WebDAV's `SEARCH` method had similar goals two decades ago and never gained traction because intermediaries failed to pass it through correctly. `QUERY` has stronger backing (Cloudflare and Akamai co-authored RFC 10008), but CDN support can't be assumed until explicitly confirmed.

Test your full request path end-to-end before relying on `QUERY` in production. Confirm that your CDN, load balancer, and API gateway pass the method and request body through intact.

## CORS Preflight Is Required

`QUERY` is intentionally not CORS-safelisted. Every cross-origin `QUERY` request triggers a preflight `OPTIONS` request. For high-frequency, latency-sensitive cross-origin calls, this adds measurable overhead. Your server must respond to `OPTIONS` with the correct headers:
```bash
HTTP/1.1 204 No Content
Allow: GET, HEAD, QUERY, OPTIONS
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, HEAD, QUERY, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
```
Setting `Access-Control-Max-Age` reduces preflight frequency by letting browsers cache the result.

### Caching Is More Complex

`QUERY` responses are cacheable per RFC 10008 §2.7, but the cache key incorporates the request body, not just the URL. This is more complex than `GET` caching and isn't natively implemented in major browsers yet. Server-side caching at the application layer or a shared cache like Redis is the practical path until broader support lands.

One pattern the RFC enables: return a `Content-Location` header pointing to a stable URI for the result set, so subsequent `GET` requests can retrieve cached results without resending the body.
```bash
HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /users/results/abc123
Cache-Control: max-age=300

[...]
```

### Not Suitable for Shareable Links

Because the query lives in the request body, users can't copy a QUERY URL and share it. If shareability matters (a search results page users are expected to bookmark or send to colleagues), stick with `GET` query parameters, or use a `POST` that redirects to a shareable result URL.

### Content-Type Is Mandatory

RFC 10008 is unambiguous: servers must reject `QUERY` requests that don't include consistent media type information. Return `400 Bad Request` for malformed or absent `Content-Type` headers, and `415 Unsupported Media` Type when the media type is valid but not supported. Don't silently accept a bodyless or untyped `QUERY`.

### `<form method="query">` Falls Back to GET

HTML forms don't support `method="query"` yet. Write `<form method="query">` today and browsers silently fall back to `GET`, dropping the body entirely. A WHATWG proposal exists at [github.com/whatwg/html/issues/12594](github.com/whatwg/html/issues/12594), but it hasn't been merged.

### Conditional Requests

QUERY supports conditional requests via standard headers (`If-None-Match`, `If-Modified-Since`). A server can return `304 Not Modified` if the query result hasn't changed. This requires tracking ETags or modification timestamps per query on the server side, which adds implementation complexity. It's not required by the spec, but it's the right pattern for cache validation once caching infrastructure matures.

## Current Adoption Status

As of July 2026:

**Spec:** RFC 10008 is published as a Proposed Standard. The IANA method registry includes `QUERY`.

**Server-side libraries:**

- **Node.js undici** - pull request open ([nodejs/undici#5459](https://github.com/nodejs/undici/pull/5459)), includes a body-aware cache key implementation

- **Eclipse Jetty** - pull request open ([jetty/jetty.project#15316](https://github.com/jetty/jetty.project/pull/15316))

- **Apache Tomcat** - pull request open ([apache/tomcat#1026](https://github.com/apache/tomcat/pull/1026))

**Protocols adopting QUERY:**

- The W3C Linked Web Storage protocol is adopting QUERY for its search and type-index functionality ([w3c/lws-protocol#179](https://github.com/w3c/lws-protocol/pull/179))

**Browser positions:** Mozilla and WebKit standards positions were requested in late June and early July 2026. Neither has shipped native `QUERY` support. The Fetch API will send the method; caching and form integration are still pending.

**CDN and proxy support:** Cloudflare and Akamai co-authored the RFC. CDN-level pass-through support is expected before most framework integrations ship, but no public shipping dates have been announced.

**Practical guidance:**

- **Full-stack apps where you control the entire path:** You can add `QUERY` support on the server today. Use `fetch()` on the client and implement application-level caching.

- **Public APIs:** Wait for CDN and proxy support to become mainstream before exposing `QUERY` to external consumers.

- **GraphQL:** `QUERY` is a natural fit for read-only GraphQL operations. Watch for adoption in major GraphQL server libraries.

## Conclusion

`QUERY` closes a specific, long-standing gap in HTTP: a safe, idempotent, cacheable operation with a request body.

Developers have been bridging this gap with `POST` workarounds for years. Every GraphQL API using POST for read-only queries, every search endpoint hiding behind a custom URL pattern, all of them are working around a problem that `QUERY` now solves at the protocol level.

The spec is solid and well-authored. As of mid-2026, you can start adding `QUERY` support to server-side applications you fully control, while tracking CDN, proxy, and browser implementation progress before rolling it out to public APIs.

For APIs with complex filter payloads, analytics dashboards, report builders, and search endpoints, `QUERY` is the most significant HTTP addition since `PATCH`. Worth understanding now and adopting when your stack is ready.

**Further reading:**

- [RFC 10008 - The HTTP QUERY Method](https://www.rfc-editor.org/rfc/rfc10008.html)
- [Mozilla Standards Position- RFC 10008](https://github.com/mozilla/standards-positions/issues/1430)
- [WHATWG HTML form method="query" proposal](https://github.com/whatwg/html/issues/12594)
- [RFC 10008 adoption tracker](https://github.com/whatwg/html/issues/12594)