The other day I came across the news that QUERY had been added as an HTTP method. My first reaction was something like, oh, so now GET can have a body. That's more or less what I expected. Still, I got curious about what it actually means in practice, so I finally got around to looking into it.

Here's what the thing looks like.

QUERY /orders HTTP/1.1
Content-Type: application/json

{
  "status": ["paid", "completed"],
  "createdAt": {
    "from": "2026-07-01",
    "to": "2026-07-31"
  }
}

Roughly speaking, it's an HTTP method for making safe queries with a body. It was standardized as RFC 10008 in June 2026. On its way from client to server, an HTTP request passes through a whole series of checkpoints. The method is the declaration form you hand over at each one, and this is the story of a new form being added to the stack.

The Days of Making Do with GET or POST

Until now, I often built APIs like this in practice (I've lost count of how many).

POST /orders/search
Content-Type: application/json

{
  "status": ["paid", "completed"],
  "from": "2026-07-01",
  "to": "2026-07-31",
  "sort": {
    "field": "createdAt",
    "order": "desc"
  }
}

What it does is search, so by nature it's close to a read. Still, once the conditions get complex, passing them in the request body is easier than cramming them into the URL. Sometimes, for instance, you want to fetch only specific IDs.

{
  "includeIds": ["order_001", "order_002", "order_003"]
}

Conversely, you might want to exclude specific IDs.

{
  "excludeIds": ["order_999", "order_888"]
}

Try to express this with query parameters alone, and you get something like the following.

GET /orders?includeIds=order_001&includeIds=order_002&includeIds=order_003

This much is still bearable. But as the number of IDs grows or they get combined with other search conditions, the URL becomes harder and harder to read.

GET /orders?status=paid&includeIds=order_001&includeIds=order_002&excludeIds=order_999&sort=createdAt_desc

Nest the search conditions, and things get messier still. Add specifications like "match any of these condition groups" or "exclude this condition," and expressing it all with query parameters becomes genuinely painful. In the past, I've also run into URL length limits.

So in practice, we'd sometimes use a shape like POST /search and pass the search conditions as JSON.
POST /orders/search
Content-Type: application/json

{
  "includeIds": ["order_001", "order_002"],
  "excludeIds": ["order_999"],
  "filters": {
    "status": ["paid", "completed"],
    "createdAt": {
      "from": "2026-07-01",
      "to": "2026-07-31"
    }
  },
  "sort": [
    {
      "field": "createdAt",
      "order": "desc"
    }
  ]
}
As an API, this shape is very easy to work with. The frontend can turn the search form's state straight into JSON, and the backend can validate it easily. That said, a certain queasiness is hard to deny. POST generally carries a strong association with "create" or "change state." All we're doing is searching, yet the method says POST, and that mismatch never quite sits right.

Is POST Really Fine for a Search?

POST /orders/search is perfectly common in practice. But POST is usually understood as a method that creates something, processes something, or might change state.

Of course, POST doesn't mean state must change. Using POST for a search does no actual harm as long as the server doesn't change state. The problem is that clients, proxies, and caches, looking only at the method, have a hard time telling whether it's a safe read. The officials at the checkpoint only read the front of the form.

For example, if the network drops midway, is it OK to retry automatically? Is it OK to cache? Can it be re-executed without side effects? With GET, you can basically assume a safe read. With POST, you can't tell without knowing the server's particular circumstances. So POST /search is convenient, but a bit ambiguous in terms of HTTP semantics. QUERY, as I understand it, is what came along to fill that ambiguity.

A Safe Query with a Body

RFC 10008 defines QUERY as follows. It processes the content enclosed in the request against the target resource in a safe and idempotent manner, and returns the result. Safe, roughly, means it doesn't change the server's state. Idempotent means that executing the same request any number of times has the same properties in terms of side effects. In short, QUERY can send a body like POST, but semantically it leans toward reading and querying.
QUERY /orders
Content-Type: application/json

{
  "status": ["paid", "completed"],
  "createdAt": {
    "from": "2026-07-01",
    "to": "2026-07-31"
  }
}
This reads very naturally. It's simply querying /orders with the conditions given in the body. It isn't creating or updating any orders. What we used to write as POST /orders/search can now be expressed more accurately as an HTTP method. At last, the form can state the honest purpose of the trip.

Safe Method with Body

What's interesting is that before it became an RFC, the draft was named safe-method-w-body. The motivation was clear from the start. People wanted a safe HTTP method that could carry a body. GET is safe, but not suited to putting complex conditions in a body. POST can carry a body, but isn't necessarily safe. And QUERY can send a body and be expressed as a safe query. The use case sitting between GET and POST has existed in practice all along, and QUERY is aiming squarely at that gap.

Why Not Just GET with a Body?

A natural question comes up here. Couldn't we just have added a body to GET?
GET /orders HTTP/1.1
Content-Type: application/json

{
  "status": ["paid"]
}

On looks alone, this seems fine. But GET bodies have historically been handled inconsistently. The practical reason is even simpler: servers, proxies, libraries, and browser APIs won't necessarily handle it the way you expect. It might be ignored. It might get dropped by middleware along the way, or the client library might not be able to send it at all.

It's tricky from a caching standpoint, too. GET is basically handled around the URI, so how to treat requests with the same URI but different bodies tends to vary by implementation. Rather than forcibly stretching the meaning of GET, it's clearer to define a new safe query method that can carry a body. That, presumably, is the reasoning behind QUERY. As a name, SEARCH might seem fine. For a search API, SEARCH /orders might even feel more intuitive. But HTTP already has existing methods such as SEARCH from WebDAV. And what this is meant to do isn't merely searching. It's making a query with a body against the target resource. Seen that way, QUERY covers more ground: beyond search, it can encompass aggregation, transformation, filtering, executing a query language, and so on. For example, something like the following could also fall within QUERY's scope.
QUERY /analytics
Content-Type: application/json

{
  "groupBy": "month",
  "metrics": ["sales", "orders"],
  "where": {
    "country": "JP"
  }
}
This is closer to a query than a search. In that sense, the name QUERY is quite natural.

The Accept-Query Header

QUERY also comes with a response header called Accept-Query. It lets the server indicate that this resource accepts QUERY in a given format.
HTTP/1.1 200 OK
Accept-Query: application/json
From this, the client knows it can send a QUERY with an application/json body to this resource. How much this gets used in practice will depend on future support. At least on the spec side, thought has been given to how QUERY can be discovered.

Caching: Possible, but Harder Than GET

Because QUERY is safe and idempotent, compatibility with caching was kept in mind. It isn't as simple as GET, though. For GET, most caches build the cache key around the URI. With QUERY, the query conditions live in the body, so two requests to the same /orders are different queries if their bodies differ.

Here, for instance, is a request asking for paid orders.

QUERY /orders
Content-Type: application/json

{ "status": "paid" }

And here is one asking for cancelled orders.

QUERY /orders
Content-Type: application/json

{ "status": "cancelled" }
These two should return different results despite sharing a URI. So caching QUERY means considering not just the URI but the request body and related metadata as well. It's cacheable by spec, but harder to implement than GET. This is something to watch out for in practice.

The CORS Preflight

When using QUERY from a browser, you also have to think about the CORS preflight. QUERY isn't treated as a so-called simple request the way GET and some POST requests are. So sending it to another origin fires an OPTIONS request first. The API side not only has to accept QUERY; the CORS configuration has to allow the QUERY method as well.
Access-Control-Allow-Methods: GET, POST, QUERY, OPTIONS
It's like having your papers checked once more before you even reach the real checkpoint. This, too, is no small hurdle for using QUERY in practice.

Should You Use It Right Away?

So should you switch everything to QUERY starting tomorrow? Caution is fine here. It has been standardized as a spec, but in practice the surrounding support matters far more than the HTTP method itself. Browsers, clients like fetch and axios, API gateways, CDNs, WAFs, reverse proxies, web frameworks. Documentation generators like OpenAPI, logging platforms, monitoring tools. All of them need to pass QUERY through without trouble. Even if it's correct as an HTTP method, it's useless if something along the way rejects it as an unknown method. Count the checkpoints a request has to clear, and the view is rather majestic. Especially for public APIs, or APIs hit by many different clients, there are still plenty of situations where POST /search is the safer bet. A single checkpoint that doesn't recognize the form is enough to stop you in your tracks.

A Name for a Previously Ambiguous Design

The value of QUERY doesn't lie in replacing POST-based search right now. As I see it, it lies in giving a name to a design that used to be ambiguous.

Until now, search APIs had roughly two options. For a simple search, you could just write it plainly with GET.

GET /orders?status=paid

For a complex search, in practice it was usually written with POST.

POST /orders/search
Content-Type: application/json

{
  "status": "paid"
}
And from here on, QUERY joins as a third option.
QUERY /orders
Content-Type: application/json

{
  "status": "paid"
}

Sorted out, the division of labor looks like this.

What you want to doSuitable method
Simple retrieval expressible in the URLGET
Safe query with a bodyQUERY
Creation, updates, or processing with side effectsPOST
This breakdown is very easy to grasp. In particular, QUERY fits APIs whose search conditions are complex but which are clearly reads in meaning.

Seen as REST API Design

In REST API design, the question "is this a resource or an operation?" comes up a lot. Take POST /orders/search: natural enough in practice. But with the operation name search sitting in the path, it looks a little RPC-ish. With QUERY, on the other hand, you can write QUERY /orders. The target is strictly /orders, and you're querying that collection of order resources. As a division of roles between HTTP method and resource, this looks cleaner.

Of course, clean isn't always better. In practice, support and the team's understanding often matter more. Even so, as a design it's quite convincing.

Was POST Search a Mistake?

So was POST /search a mistake all along? Personally, I don't think so. If anything, it was a very reasonable design given the realistic constraints.

With GET, URLs got too long. GET with a body raised implementation worries. Complex search conditions wanted to be sent as JSON. Under those conditions, POST was the only option left.

QUERY doesn't negate POST search. It's best seen as making part of the role POST search played officially expressible in HTTP. Until now we substituted POST; from now on, there will be situations where QUERY fits better in meaning. That level of enthusiasm feels about right. That's what I found looking into the HTTP QUERY method. GET suits simple retrieval expressible in a URL. POST suits processing with side effects, such as creation and modification. In between sat read APIs that merely want to send complex search conditions in a body, and until now we've muddled through with POST /search. QUERY is the method that gives that middle ground a name and a meaning. That said, standardization doesn't mean the surroundings will catch up immediately. The lineup of checkpoints, from CDNs and WAFs to API gateways, frameworks, and client libraries, isn't going to turn over in a hurry. For now, here's where I've landed. GET for simple searches, POST for complex searches where compatibility matters, and QUERY when you want to express the HTTP semantics accurately. QUERY won't become mainstream overnight. What it does is give the body-carrying search APIs we've been building somewhat by feel an honest form to fill out.

References