When I started learning backend development, GET and POST felt simple. GET to read data, POST to send data. But building a few real search and filter features made me realize it's not that clean in practice.
Here's the issue I kept running into.
The problem with GET
GET is safe and idempotent. That means calling it multiple times never changes anything on the server, so it's always safe to cache or retry.
But all the data has to go in the URL as query parameters.
GET /products?category=electronics&price_min=100&price_max=500&brand=sony&brand=apple&in_stock=true&sort=price_desc
Once filters get even a little complex, the URL turns messy fast, hits length limits on some proxies, and gets logged more often than a request body would. Not great if any of that data is sensitive.
The problem with POST
POST lets you send a proper request body, so no size or structure problems there.
POST /products/search
Content-Type: application/json
{
"category": "electronics",
"price_range": { "min": 100, "max": 500 },
"brands": ["sony", "apple"],
"in_stock": true,
"sort": "price_desc"
}
Much cleaner. But here's the catch: the protocol has no way of knowing this POST is just reading data. It gets treated like it might change something on the server. That means:
- No caching, since caches don't trust POST to be safe
- No automatic retry if the connection drops, since retrying could accidentally repeat a state-changing action So a lot of APIs (including ones I've seen and built) use POST for search endpoints anyway, even though it's technically not meant for that. It works, but you lose the caching and safety guarantees GET was designed to give you.
Enter QUERY
This is the exact gap the new QUERY method (standardized as RFC 10008 in June 2026) is meant to close.
QUERY /products
Content-Type: application/json
Accept: application/json
{
"category": "electronics",
"price_range": { "min": 100, "max": 500 },
"brands": ["sony", "apple"],
"in_stock": true,
"sort": "price_desc"
}
Or with curl:
curl -X QUERY https://api.example.com/products \
-H "Content-Type: application/json" \
-d '{
"category": "electronics",
"price_range": { "min": 100, "max": 500 },
"brands": ["sony", "apple"],
"in_stock": true,
"sort": "price_desc"
}'
QUERY lets you send a full request body, like POST, but keeps the safety and idempotency of GET. So:
- Responses can be cached
- Requests can be retried safely without worrying about side effects
- Servers can advertise support via the
Allowheader, and declare accepted formats via the newAccept-Queryheader Basically, it's a proper middle ground between the two methods I thought I already understood.
A couple of things worth knowing before using it
Cache keys need to include the request body. Since QUERY is cacheable but the "query" itself lives in the body rather than the URL, a cache that only keys off the URL will serve the wrong response to the wrong request. This is a real gotcha for anyone rolling out edge caching with QUERY.
Adoption is still early. HTML forms still only support GET and POST, so browsers need to catch up before you can use QUERY in a plain <form>. Some older WAFs, proxies, and API gateways built around the classic method list (GET/POST/PUT/DELETE/PATCH) might not recognize QUERY yet either, so it's worth checking your infrastructure before relying on it in production.
Why I'm writing this
Honestly, writing this out helped me understand the "why" behind QUERY, not just the definition. If you've been quietly abusing POST for search endpoints (no judgment, I have too), this is worth keeping an eye on.
Curious if anyone here has already tried implementing a QUERY endpoint, or run into it while integrating with an API. Would love to hear how it went. 🙌

Top comments (0)