Skip to content

What is the HTTP QUERY method

Comparison diagram: GET (body ❌, safe ✅), POST (body ✅, safe ❌), QUERY (body ✅, safe ✅)

The QUERY method is an HTTP request that sends the query in the request body while keeping GET’s safety guarantees.

QUERY = GET with a body. Safe, idempotent, cacheable, but with a request body. The POST /search hack resolved at the protocol level.


sidebar: label: Introduction order: 1

RFC 10008, published June 15, 2026. First new HTTP method since PATCH (RFC 5789, 2010). Sixteen years without a new method.

“A QUERY requests that the request target process the enclosed content in a safe and idempotent manner and then respond with the result of that processing.”

RFC 10008, Section 1

Authors: Julian Reschke (greenbytes), James Snell (Cloudflare), Mike Bishop (Akamai).

The spec started life as SEARCH in 2015. Renamed November 2021 to escape WebDAV baggage. Eleven years from first draft to standard.


sidebar: label: Introduction order: 1

Property Value What it means in practice
Safe Yes No server state changes. Proxies and crawlers can fire it freely.
Idempotent Yes Same request, same result. Automatic retry is safe.
Cacheable Yes Responses can be cached. Cache key includes body + Content-Type.
Request body Expected The body IS the query. Content-Type is mandatory.

The body defines the question. The Content-Type defines the question’s format. Servers MUST reject requests with missing or inconsistent Content-Type.

Servers advertise support via the Accept-Query header:

Accept-Query: application/json, application/graphql

sidebar: label: Introduction order: 1

GET POST QUERY
Safe
Idempotent
Cacheable Only for future GET/HEAD
Request body No defined semantics Expected Expected
Auto-retry ❌ (may cause side effects)
Practical limit ~8,000 octets in URL No limit No limit
Log leakage Query params in access logs Body stays out of logs Body stays out of logs

RFC 9110 recommends implementations support URIs of at least 8,000 octets. That’s a floor, not a guarantee. Every proxy, load balancer, and server in the chain has its own limit. You discover the smallest one at runtime — usually via a 414 behind a corporate proxy your test suite will never reproduce.

URLs also leak. Access logs, bookmarks, browser history. If the query contains sensitive data, it’s now in every log along the path.

POST works for queries until you need retries, caching, or honest semantics.

POST is not safe. POST is not idempotent. When the connection drops mid-request, no client or proxy knows whether state changed on the server. Nothing retries automatically. You end up building idempotency keys by hand — machinery that reads should never have needed.

Caching? RFC 9110 allows caching POST responses only with explicit freshness plus Content-Location matching, and even then it only serves future GET/HEAD. Two identical POST /search calls always hit your origin.


sidebar: label: Introduction order: 1

  • Complex filters that won’t fit in the URL (nested JSON, arrays, date ranges)
  • Queries with sensitive data that shouldn’t leak into logs
  • Search endpoints currently abusing POST for reads
  • JSON-RPC and APIs where every call is semantically a read
  • Any read-only request that needs a body
  • Simple queries that fit comfortably in the URL — GET is still better (simpler, universally supported)
  • State-changing operations — use POST/PUT/PATCH/DELETE
  • Public APIs where legacy clients don’t support QUERY — keep POST as a fallback
  • Cross-origin browser requests where the preflight OPTIONS round-trip is unacceptable for your latency budget

sidebar: label: Introduction order: 1

The QUERY response is cacheable, but the cache key MUST incorporate the request content, not just the URI. Section 2.7 of the RFC is explicit.

This is harder than caching GET. The cache must buffer the entire body before deciding whether it’s seen this request before. Caches may normalize (reorder JSON keys, strip whitespace) to improve hit rates.

The risk: over-aggressive normalization produces false positives — the cache returns the wrong query’s results.

Conditional requests work via ETag and If-None-Match, operating against the “equivalent resource” — a hypothetical GETable resource derived by incorporating the request body into the target.


sidebar: label: Introduction order: 1

Terminal window
curl -X QUERY 'http://localhost:3000/contacts' \
-H 'Content-Type: application/json' \
-d '{"filter": {"city": "Berlin"}, "limit": 50}'

What used to be:

GET /contacts?filter[city]=Berlin&limit=50

Or, when it got too long:

Terminal window
POST /contacts/search
Content-Type: application/json
{"filter": {"city": "Berlin", "age_gte": 25, "tags": ["vip", "active"]}, "limit": 50}

Now has the right method: a read that behaves like a read.


sidebar: label: Introduction order: 1

Stack Status
Node.js Native since Node 21.7.2 / 22+ (llhttp 9.2.0)
Fastify fastify.addHttpMethod('QUERY', { hasBody: true })
Go net/http Works with arbitrary method strings
.NET .NET 11 Preview 4 recognizes QUERY
Spring PR #34993 open, blocked
OpenAPI 3.2.0 (Sep 2025) — first-class query field
curl -X QUERY with --data and explicit Content-Type
Browsers fetch() accepts it, but CORS preflight mandatory

CDNs: two of the RFC’s three authors work at Cloudflare and Akamai. Good sign. Official edge cache support? Not confirmed yet.


sidebar: label: Introduction order: 1