RFC 10008 landed, and it fixes a hole we have all been stepping around for twenty years. QUERY sends its payload in the request body like POST, but it is declared safe and idempotent like GET, so caches and retry logic are allowed to trust it. It comes with a discovery story — OPTIONS plus an Accept-Query header telling you which media types the endpoint will accept — and with Location and Content-Location on the response so a cache has something to key on. That is a clean, small design. My position: start supporting it in your APIs now, behind a POST fallback, because the specification was never the hard part. The hard part is every box between your client and your PHP process, and those boxes only learn new methods when somebody makes them.
Let me be concrete about the pain QUERY is meant to end, because it is not theoretical. You have a search endpoint. The filter object is a nested structure with fifteen facets, a bounding box and a sort spec. Put it in the query string and you are one saved-search away from a 414, your access logs quietly archive customer email addresses forever, and your CDN treats every reordering of the same parameters as a different cache key so the hit rate sits at fifteen percent. So you switch to POST. Now the URL is clean and the log is clean, but you have told every intermediary on the path that this request might change state. Varnish will not cache it. Your HTTP client will not retry it on a connection reset, which means one flaky upstream turns into a user-visible error instead of a hiccup. You did not solve the problem; you traded it for a different one and stopped thinking about it.
The honest counter-argument deserves better than a shrug, so here it is at full strength. POST works. It works everywhere, in every proxy, in every corporate WAF, in every SDK your customers already wrote against your API. Adding a third code path so that a search request can theoretically be cached is churn, and most search results are personalised anyway, which kills shared caching before QUERY gets a chance. Meanwhile browsers will not help you: an HTML form still only emits GET or POST, so QUERY only exists for fetch and server-to-server traffic. And unknown methods are where infrastructure goes to die — an ALB with a method allowlist, a WAF whose default rule set drops anything outside the classic seven, a corporate proxy that answers 501 without a body. Every one of those is a real Tuesday afternoon you will not get back.
I still land on adopting it, for a reason that has little to do with cache hit rates. QUERY lets you say what you mean at the protocol level. Right now, the difference between 'this POST reads data' and 'this POST charges a credit card' lives in a README, a route name, and the memory of whoever wrote the client. Nothing in the wire format distinguishes them, so every generic layer — retry middleware, circuit breakers, request replay in your debugging proxy, the idempotency logic in your job queue — has to assume the dangerous case. Semantics you can only express in prose are semantics you cannot automate. QUERY turns a convention into a fact the machine can act on, and that compounds long after the caching argument gets boring.
For the PHP side, the good news is that most of our stack does not care what the method string is. PSR-7 and PSR-15 treat it as an opaque token, Guzzle will send whatever you hand it, and your controller reads php://input instead of $_POST either way. The friction is in the specific places that hardcode the classic list: Symfony's HttpFoundation has explicit sets for which methods count as safe and cacheable, Laravel's router validates against known verbs, and anything that reconstructs a request from globals needs to know that neither $_GET nor $_POST will be populated for you. None of that is deep surgery, but it is the kind of work that only gets done if someone opens the issue. If you maintain a router, an HTTP client wrapper or an API skeleton, this is a genuinely good weekend contribution — small diff, long shelf life.
The pattern I would actually ship in production this quarter is unglamorous and boring on purpose. Register the same handler for both QUERY and POST on your search and report endpoints. Answer OPTIONS honestly with the methods and the Accept-Query media types you support, so a client can probe once and cache the answer. On the client side, try QUERY, and on a 405 or 501 fall back to POST for that host and remember the result — do not pay the failed round trip on every call. Then check your edge: whether nginx forwards the method to FastCGI untouched, whether your CDN's allowlist includes it, whether your WAF's default action for unknown methods is block or pass. Half an hour of curl against staging will tell you more about your real adoption timeline than any spec discussion.
The thing I genuinely do not know is where the tipping point sits. Nobody deploys QUERY support until clients send it, and no client sends it until servers deploy it, and neither happens while the CDN in the middle answers 501. Somebody has to move first at a loss. So: if you run a public API today, would you enable QUERY alongside POST this year, or do you wait until your CDN vendor announces support in a changelog — and if you already tried, which box in your stack broke first?
Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Waiting for your click …
·