Skip to main content
On this page

The New HTTP QUERY Method

  • http
  • webdev
  • api
  • backend
The word QUERY rendered as an HTTP request line

If you’ve ever built a search endpoint, you’ve hit this wall. Your query has filters, sort orders, a nested set of facets, maybe a geo bounding box. It doesn’t fit in a URL, and cramming it into query string params is ugly and fragile. So you reach for POST /search, send the whole thing as a JSON body, and quietly accept that you’ve just lied about what the request does. It’s not creating anything. It’s a read. But POST is the only tool that lets you attach a body without fighting the platform.

That gap finally got filled. In June 2026 the IETF published RFC 10008, which defines the HTTP QUERY method: a new verb built for exactly this case.

The two bad options

Every read that needs structured input has been stuck choosing between GET and POST, and both are wrong in their own way.

GET is the semantically correct choice. It’s safe (the client isn’t asking to change anything), it’s idempotent (retrying it is fine), and it’s cacheable. The problem is the body. RFC 9110 is explicit that content in a GET request has no defined semantics, and sending one may cause some implementations to reject the request. So your query has to live in the URI, where you run into unknown length limits across proxies and servers, encoding overhead, and the query landing in access logs and browser history.

POST solves the body problem and creates a new one. It carries any payload you want, but it’s neither safe nor idempotent by definition. Intermediaries won’t cache it, clients won’t retry it automatically after a dropped connection, and anything inspecting traffic has to assume the request might have side effects. You get the body, you lose everything that made the request honest.

QUERY is the missing third option: a method that carries a body and keeps the semantics of a read.

What QUERY actually is

The spec, authored by Julian Reschke, James Snell, and Mike Bishop, describes it in one sentence:

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.

So a QUERY request looks like this:

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

{
  "filters": { "category": "keyboards", "inStock": true },
  "sort": [{ "field": "price", "order": "asc" }],
  "page": { "size": 20 }
}

The three properties that matter:

It’s safe and idempotent. The client isn’t requesting a state change, and the request can be retried or repeated without worrying about partial effects. This is what POST could never promise.

It’s cacheable. A cache is allowed to store the response and use it to satisfy later QUERY requests. The catch is that the cache key has to include the request content, not just the URI, since the body is what distinguishes one query from another. Two QUERYs to the same URL with different bodies are different requests.

The response isn’t a representation of the URI. Unlike GET, where the response is the resource at that URL, a QUERY response is the result of running your query over some data scoped to the target. GET /products returns the products resource; QUERY /products returns whatever your query selected from it.

The details that bite

A few rules are worth knowing before you wire this up.

Content-Type is mandatory. The server MUST reject the request if the Content-Type field is missing or inconsistent with the body. In practice you’ll see a 400 for a missing media type, a 415 Unsupported Media Type for one the server doesn’t handle, and a 422 Unprocessable Content when the body parses fine but doesn’t make sense as a query.

There are two response headers that trip people up because they sound alike:

  • Content-Location points at a resource representing the results of this query. A client can GET that URL later to fetch the same results again.
  • Location points at a resource representing the query itself. A client can GET that URL to re-run the query without resending the body, which is handy for turning a heavy query into a shareable link.

Results points to the answer; Location points to the question.

The spec also nudges you away from HTTP range requests for pagination. Range semantics technically apply, but most query formats already have their own paging built in (think SQL’s FETCH FIRST), and that’s the mechanism you should reach for.

Can you use HTTP QUERY today?

On the client, yes, already. The Fetch API and libraries like axios accept arbitrary method strings, so nothing is stopping you from sending one right now:

const res = await fetch("/products", {
  method: "QUERY",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ filters: { category: "keyboards" } }),
});

The friction is everywhere else in the stack. QUERY isn’t a CORS-safelisted method, so cross-origin QUERY requests trigger a preflight OPTIONS. Your server has to answer it with Access-Control-Allow-Methods: QUERY or the browser blocks the real request. And anything that whitelists methods will reject QUERY until you tell it not to: reverse proxies, WAFs, API gateways, limit_except blocks in nginx. The method passing through the wire is the easy part; the config that guards the wire is where you’ll spend your time.

Framework and server support is still landing. New HTTP methods don’t come around often. The last one most people reached for was PATCH back in 2010, so the ecosystem moves slowly. Expect native routing helpers and middleware to fill in over the next couple of years rather than overnight.

Should you rush to switch?

Probably not, and there’s no need to. Your POST /search endpoints work and aren’t going anywhere. QUERY is the more correct tool, not an urgent migration.

What it does give you is a real answer to a question we’ve been hacking around for years. When you’re designing a new read endpoint that needs a structured body, you now have a method that says exactly what it means: this is a safe, repeatable, cacheable read, and here’s the query in the body where it belongs. That’s worth reaching for on the next thing you build, even if the old endpoints stay put.