HTTP QUERY Is Official: Should You Replace POST /search Yet?
For years, API developers have written endpoints like this:
POST /products/search HTTP/1.1
Content-Type: application/json
{
"filters": {
"category": ["laptops", "tablets"],
"price": { "min": 500, "max": 2000 },
"inStock": true
},
"sort": [
{ "field": "rating", "direction": "desc" }
],
"page": { "size": 50 }
}
Nothing is being created. Nothing is being updated.
The client is asking a complicated question, but GET becomes awkward once the input turns into nested filters, ranges, sorting rules, cursors, and other structured data. So we use POST because it gives us a request body.
That workaround now has an official alternative.
In June 2026, the IETF published RFC 10008, defining a new HTTP request method called QUERY.
The same search can now be expressed as:
QUERY /products HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"filters": {
"category": ["laptops", "tablets"],
"price": { "min": 500, "max": 2000 },
"inStock": true
},
"sort": [
{ "field": "rating", "direction": "desc" }
],
"page": { "size": 50 }
}
That looks like a small syntax change.
It is not.
QUERY tells the HTTP stack that this request:
- can carry structured request content
- is safe
- is idempotent
- can be retried as a query
- has cacheable responses by specification
The interesting question in 2026 is no longer "what is QUERY?"
It is this:
Should you actually use it in production yet?
TL;DR
If you only remember five things:
-
QUERYis standardized in RFC 10008 and registered as a safe, idempotent HTTP method. - It is designed for read-only server-side queries where the input belongs in request content rather than a giant URI.
- Browser
fetch()can send it today, but cross-origin requests require CORS preflight. - QUERY responses are cacheable, but a correct cache key must include the request content. Browser caching support is still catching up.
- I would not migrate every
POST /searchendpoint today. I would consider QUERY for new APIs or controlled environments where I can test the complete request path.
That last point matters most.
A new HTTP method only works when every layer between the client and your handler agrees to let it pass.
The gap between GET and POST
A normal read fits GET perfectly:
GET /products?category=laptop&brand=apple&sort=price
It is readable, shareable, easy to inspect, and works naturally with existing caching infrastructure.
Then requirements grow.
You add:
- nested AND/OR conditions
- multiple ranges
- faceted filters
- geospatial constraints
- cursor pagination
- aggregations
- semantic search parameters
Eventually the query becomes a serialized data structure pretending to be a URL.
GET has practical URI limits
RFC 10008 points out that a request may travel through many independent systems with different URI limits. HTTP recommends support for request targets of at least 8,000 octets, but that does not give you one universal maximum across browsers, proxies, gateways, WAFs, CDNs, frameworks, and servers.
The problem is not finding a magic number.
The problem is that you do not control every hop.
Structured filters do not map cleanly to a URI
This is easy to understand:
{
"and": [
{ "category": "laptop" },
{
"or": [
{ "brand": "Framework" },
{ "repairabilityScore": { "gte": 8 } }
]
}
]
}
Encoding the same structure into query parameters usually means inventing a convention every client must learn.
URIs are highly observable
URLs commonly appear in access logs, browser history, analytics systems, monitoring tools, and intermediary infrastructure.
A request body is not automatically private. Your infrastructure can log bodies too, and TLS is still mandatory.
But RFC 10008 explicitly notes that URIs are more likely to be logged or processed by intermediaries than request content. That matters when query inputs should not casually appear in a URL.
Why not just send a GET body?
Because HTTP does not define general semantics for GET request content.
RFC 9110 warns that GET content has no generally defined semantics and may be rejected by some implementations because of request smuggling concerns.
A private agreement between your browser and origin is not enough when several intermediaries sit between them.
This is the gap QUERY closes.
Important correction: POST /search was never "invalid"
A lot of QUERY explainers make this claim:
POST is for creating things, so POST /search is wrong.
That is too simplistic.
POST is broader than "create a resource." HTTP allows the target resource to process the enclosed content according to its own semantics. APIs have legitimately used POST for complex searches for years.
The real limitation is different.
A generic HTTP component cannot look at POST /search and know that your particular operation is safe and idempotent.
With QUERY, it can.
Safe
A safe method means the client is not requesting a state change to the target resource.
The server may still write logs, metrics, cache entries, or other incidental data. "Safe" describes the requested semantics, not the absence of every server-side write.
Idempotent
Idempotent means the intended effect of repeating the same request is the same as sending it once.
That becomes important when a connection fails halfway through a request.
An intermediary cannot generically assume that retrying POST is harmless.
QUERY explicitly provides that contract.
GET vs POST vs QUERY
| Property | GET | POST | QUERY |
|---|---|---|---|
| Safe by method semantics | Yes | Not guaranteed | Yes |
| Idempotent by method semantics | Yes | Not guaranteed | Yes |
| Request content has defined query semantics | No | Resource-specific | Yes |
| Best for simple, shareable reads | Excellent | Usually unnecessary | Usually unnecessary |
| Best for large structured read inputs | Awkward | Common workaround | Designed for it |
| Response cacheable by HTTP semantics | Yes | Possible, with different rules | Yes |
| Query input outside the URI | No | Yes | Yes |
The key point is simple:
QUERY does not replace GET.
GET remains the best choice for ordinary retrieval.
QUERY becomes interesting when the operation is still a read, but the input deserves a structured representation.
Four details in RFC 10008 that developers should know
1. Content-Type matters
The target URI defines the scope of the query. The request content and media type define the query itself.
QUERY /orders HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"where": {
"status": ["paid", "shipped"]
},
"groupBy": ["country"],
"limit": 100
}
RFC 10008 requires the server to fail a QUERY request when Content-Type is missing or inconsistent with the request content.
That also gives useful error semantics:
-
400for missing or inconsistent media type -
415for an unsupported query format -
422for valid content that cannot be processed -
406when the requested response format is unavailable
2. Accept-Query advertises supported query formats
RFC 10008 introduces Accept-Query.
Accept-Query: application/json, application/sql
A client can also discover QUERY support through OPTIONS:
OPTIONS /products HTTP/1.1
Possible response:
HTTP/1.1 200 OK
Allow: GET, QUERY, OPTIONS, HEAD
Accept-Query: application/json
That gives an API a protocol-level way to say both "I support QUERY" and "these are the query representations I understand."
3. QUERY can participate in conditional requests
Because QUERY has retrieval-like semantics, it can work with validators and conditional requests.
That means a repeated expensive query can potentially use mechanisms such as If-Modified-Since and receive 304 Not Modified when appropriate.
4. A server can assign the query a GET-able URI
A successful QUERY can return a Location pointing to an equivalent resource.
HTTP/1.1 200 OK
Location: /queries/8f4b32a1
Content-Type: application/json
The client can later use:
GET /queries/8f4b32a1
This pattern is especially interesting for saved searches, analytics, expensive reports, and repeated complex queries.
Browser fetch can already send QUERY
Scripted fetch() requests can use QUERY because it is not a forbidden Fetch method.
const response = await fetch("https://api.example.com/products", {
method: "QUERY",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
filters: {
category: "laptop",
minRating: 4.5
},
limit: 20
})
});
const data = await response.json();
One small 2026 gotcha: use uppercase QUERY.
The Fetch Standard currently normalizes only a fixed set of familiar methods such as GET and POST. QUERY is not yet in that normalization list, so lowercase query may be transmitted as lowercase instead of being automatically normalized.
Tiny detail, long debugging session.
Cross-origin QUERY requires CORS preflight
QUERY is not a CORS-safelisted method.
If your frontend and API are on different origins, the browser will send a preflight request first.
OPTIONS /products HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: QUERY
Access-Control-Request-Headers: content-type
Your API needs to authorize it:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, QUERY, OPTIONS
Access-Control-Allow-Headers: Content-Type
If your CORS config has a hard-coded method list such as:
GET, POST, PUT, PATCH, DELETE
QUERY will fail even if your route handler understands it perfectly.
That leads to the biggest rollout lesson:
Application support is not the same as end-to-end support.
Caching is where QUERY gets difficult
RFC 10008 says QUERY responses are cacheable.
But there is a critical rule:
the cache key MUST incorporate the request content and related metadata.
Consider these requests:
QUERY /products
Content-Type: application/json
{ "category": "laptops" }
QUERY /products
Content-Type: application/json
{ "category": "cameras" }
The URI is identical. The query is not.
A cache that only keys on method plus URL could return laptop results for the camera request.
That is not a minor cache miss problem. It can become a data correctness or isolation problem.
A QUERY-aware cache needs something closer to:
method
+ target URI
+ request content
+ relevant representation metadata
+ Vary dimensions
The RFC allows semantically insignificant differences to be normalized before calculating the key, but normalization itself is tricky.
For example, these JSON documents may be semantically equivalent:
{"category":"laptops","limit":20}
{"limit":20,"category":"laptops"}
Generic infrastructure should not assume every query format can be normalized the same way.
The spec is currently ahead of browser caches
An open WHATWG Fetch discussion notes that scripted QUERY requests work, but tests with Chrome and Firefox did not cache repeated identical QUERY requests in the body-aware way RFC 10008 permits.
So keep these two statements separate:
QUERY is cacheable by specification.
Your browser, CDN, proxy, or gateway currently implements QUERY caching correctly.
Do not assume the second from the first.
What should you test before production?
As of August 2026, the method itself is standardized, but ecosystem support remains uneven.
| Layer | Status to expect in 2026 |
|---|---|
| RFC / IANA | Standardized and registered |
Browser fetch()
|
Can send QUERY |
| Cross-origin browser request | Requires CORS preflight |
| Browser QUERY caching | Do not rely on it yet |
| curl | Can send custom QUERY requests |
| Node.js | Verify your deployed runtime with http.METHODS
|
| Framework routing | Support is arriving, verify your exact version |
| HTML forms | Do not rely on declarative QUERY submission yet |
| CDN / WAF / gateway | Must be tested explicitly |
A valid request can still be rejected by:
- reverse proxies
- CDNs
- WAFs
- bot mitigation
- API gateways
- CORS middleware
- service meshes
- authentication middleware
- observability pipelines
If you are testing Node, start with this:
import http from "node:http";
console.log(http.METHODS.includes("QUERY"));
And curl can send the method directly:
curl -X QUERY https://api.example.com/products \
-H 'Content-Type: application/json' \
-d '{"category":"laptops","limit":20}'
But localhost success proves very little.
Run that request through the same public hostname, CDN, gateway, firewall, and application path your real users will hit.
The production rollout checklist I would use
1. Confirm the operation is truly safe
Good candidates:
- catalog search
- analytics queries
- log search
- complex resource filtering
- semantic search
- data exploration
Bad candidates:
- checkout
- sending email
- triggering deployments
- marking notifications as read
- anything whose requested semantics change application state
Do not use QUERY merely because an endpoint returns data.
2. Search the stack for method allowlists
Look for configurations like:
GET|POST|PUT|PATCH|DELETE|OPTIONS
That may be hiding in a proxy, WAF, gateway policy, router, rate limiter, or test fixture.
3. Fix CORS deliberately
For browser clients across origins, add QUERY to allowed methods and test the preflight path.
4. Decide the cache strategy before launch
I would start with one of two options:
Option A: disable shared QUERY caching until every layer is verified.
Option B: implement a body-aware cache key deliberately and test it with adversarial inputs.
Never put QUERY behind a cache that ignores the request content.
5. Update observability
Make sure dashboards do not dump QUERY into OTHER.
Track request count, latency, error rate, cache behavior, preflight failures, and edge rejections separately.
6. Keep a fallback for public APIs
For a transition period, supporting both can be reasonable:
POST /products/search compatibility endpoint
QUERY /products standards-based query endpoint
That lets early adopters use QUERY without forcing every SDK and intermediary to upgrade at once.
So, should you replace POST /search?
My answer is not everywhere, and not simply because a new RFC exists.
I would use QUERY today when:
- I am designing a new API or can evolve both client and server
- the operation is genuinely read-only
- the query is too complex for a clean GET URI
- safe retry semantics matter
- I can test the entire proxy and gateway path
- I have a deliberate caching strategy
- I can keep a fallback when interoperability matters
I would keep GET when:
- the query is small
- the URL is useful to bookmark or share
- mature CDN and browser caching matters
- maximum interoperability matters
I would keep POST when:
- compatibility is more important than protocol purity
- I cannot verify all intermediaries
- the operation is not actually safe
- migration creates more complexity than value
QUERY is not a reason to rewrite healthy APIs.
It is a better primitive for the next complex read endpoint you design.
A note from Techifive
At Techifive, we design and build custom software systems, including REST and GraphQL APIs, web applications, cloud infrastructure, edge delivery, security, and performance optimization.
HTTP QUERY is a good example of why production API design does not stop at the controller. A request crosses clients, CORS policy, gateways, caches, security layers, observability systems, and application code. A standards-compliant endpoint can still fail if one layer in that chain is not ready.
For teams modernizing an API platform, the useful question is not only "does my framework support this?" It is "does the complete production path support it safely and predictably?"
That is the level at which architecture decisions should be evaluated.
Final takeaway
QUERY is now a real part of HTTP, not an experimental naming convention.
It gives developers something that was missing for a long time: a standardized way to send structured query content while explicitly preserving safe and idempotent semantics.
But standards adoption happens in layers.
Your browser may send QUERY while your CORS policy blocks it.
Your application may route it while your WAF rejects it.
Your origin may understand it while your cache keys it incorrectly.
So yes, learn it.
Yes, prototype it.
And for the right new API, consider using it.
Just test the complete request path before replacing every POST /search in production.
If you have already tested HTTP QUERY, which layer in your stack was the first to reject it?
That answer is probably more useful to other developers right now than another explanation of what an HTTP method is.
Top comments (0)