
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.
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.
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.
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.
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.
When query parameters become too large or too structured for a URL, developers reach for POST:
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.
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.
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.
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.
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.
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.
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. |
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:
POST /products/search
versus:
QUERY /products
The second says more with less.
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:
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.
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 /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.
RFC 10008 doesn't prescribe a body format. SQL is a valid content type when client and server agree:
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 /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.
Express doesn't ship with a built-in router method for QUERY. Use app.all() and filter by req.method:
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.
QUERY isn't a forbidden method in the Fetch spec and won't be normalized away. fetch() sends it as-is:
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.
Before sending a QUERY request to an unknown server, probe for support first:
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.

| 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) |
This is the part most enthusiastic blog posts skip. Read it before touching production.
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.
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:
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.
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.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /users/results/abc123
Cache-Control: max-age=300
[...]
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.
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 GETHTML 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, but it hasn't been merged.
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.
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), includes a body-aware cache key implementation
Eclipse Jetty - pull request open (jetty/jetty.project#15316)
Apache Tomcat - pull request open (apache/tomcat#1026)
Protocols adopting QUERY:
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.
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: