HomeThe HTTP QUERY method: GET semantics with a request body

The HTTP QUERY method: GET semantics with a request body

By · Node.js & JavaScript developer
Published July 5, 2026

For decades HTTP has forced an awkward choice for "read" endpoints that need a lot of input: use GET and cram everything into the query string (which is safe, idempotent, and cacheable — but limited in size and awkward for structured data), or use POST with a body (which fits any payload — but is treated as unsafe and non-idempotent, so it can't be cached or safely retried). The new QUERY method finally closes that gap.

QUERY gives you the semantics of GET with the request body of POST. You send a request body describing what you want, the server processes it and returns the result, and — crucially — the method is registered as safe and idempotent, so any generic client, proxy, or cache can treat it that way.

Is it official? Yes — RFC 10008

QUERY was published as RFC 10008 in June 2026, on the IETF Proposed Standard track. It came out of the long-running draft-ietf-httpbis-safe-method-w-body draft, authored by Julian Reschke, James Snell (Cloudflare), and Mike Bishop (Akamai). It's registered in the IANA HTTP Method Registry as safe and idempotent.

The spec also introduces a companion response header, Accept-Query, which lets a resource advertise the query languages (formats) it understands, so clients can discover how to talk to an endpoint.

Why not just use GET or POST?

  • GET has no defined body semantics and puts everything in the URL. URLs have practical length limits, and encoding structured filters (nested JSON, geometry, long ID lists) into a query string is painful and leaks data into logs and browser history.
  • POST takes any body, but it's defined as unsafe and non-idempotent. Caches won't cache it, and intermediaries won't automatically retry it — so you lose exactly the properties a read operation should have.
  • QUERY takes an arbitrary body and is safe + idempotent, so responses are cacheable and requests are automatically retryable. It's the right tool for "a read that needs a real payload."

Can I use it in the browser?

Partly — and this is the part that trips people up.

You cannot submit QUERY from an HTML <form>. The form method attribute only accepts get, post, or dialog; anything else silently falls back to GET. There is no pure-HTML way to send a QUERY request.

You can send it from JavaScript. QUERY is a valid method token and isn't on the Fetch spec's forbidden list (CONNECT, TRACE, TRACK), so fetch() and XMLHttpRequest accept it. Two things to remember:

  1. Write it in uppercase — 'QUERY'. Fetch only case-normalizes the well-known methods, not this one.
  2. Cross-origin, QUERY is not CORS-safelisted, so it triggers a preflight OPTIONS request. Your server must answer that with Access-Control-Allow-Methods: QUERY.

What about servers?

Support is still early and uneven. Because QUERY is a brand-new method, many stacks reject unknown methods before your handler ever runs — for example, Rails' Action Pack rejects QUERY unless explicit framework support is added, and Express has no app.query() routing helper, so you handle it manually. Intermediaries matter too: some proxies, CDNs, and WAFs still drop requests with unfamiliar methods. Expect it to take a while before it works end-to-end everywhere.

A minimal example you can actually test

Since a plain form can't do it, the practical pattern is a normal <form> whose submit you intercept and resend with fetch(). Here's the client:

<form id="q">
  <input name="term" placeholder="search term" />
  <button>Query</button>
</form>
<pre id="out"></pre>

<script>
  const form = document.getElementById('q');
  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    // Encode the form fields as the request body
    const body = new URLSearchParams(new FormData(form)).toString();
    const res = await fetch('/search', {
      method: 'QUERY',                                     // uppercase
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body,                                                // the query lives in the body
    });
    document.getElementById('out').textContent = await res.text();
  });
</script>

And a minimal Node.js server to test against — plain node:http, so no framework strips the method:

import http from 'node:http';

http.createServer((req, res) => {
  if (req.method === 'QUERY' && req.url === '/search') {
    let body = '';
    req.on('data', (chunk) => (body += chunk));
    req.on('end', () => {
      const params = new URLSearchParams(body);
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ youSearchedFor: params.get('term') }));
    });
    return;
  }
  res.writeHead(404).end();
}).listen(3000, () => console.log('Listening on http://localhost:3000'));

Because the request is same-origin, there's no preflight to worry about. If you call it from another origin, add CORS handling for the OPTIONS preflight and include QUERY in Access-Control-Allow-Methods.

A caching caveat

QUERY responses are cacheable, but the cache key has to include the normalized request body, not just the URI. That means a cache must read and canonicalize the entire body before it can even check for a hit — a meaningfully harder job than caching a GET by URL. This is one reason shared caches and CDNs will be slow to support it.

The long road to full support

A new HTTP method isn't a switch someone flips — it's a change that has to ripple through the entire stack of software a request touches on its way from the browser to your code and back. A single request can pass through a browser, a CDN edge, a load balancer, a reverse proxy, a WAF, an API gateway, and finally your application framework. If any hop in that chain doesn't recognize QUERY, it may reject it (often with a 400 or 405), strip the body, or silently rewrite it. That's why realistic, end-to-end support is measured in years, not weeks.

Here's the (non-exhaustive) list of software categories that need updates — and, just as importantly, that need operators to actually deploy those updates:

  • Web servers & reverse proxies — Apache httpd, Nginx, Caddy, HAProxy, Envoy, Traefik. Many have method allow-lists or reject unknown methods by default.
  • CDNs & edge networks — Cloudflare, Fastly, Akamai, Amazon CloudFront, Google Cloud CDN. Beyond passing the method through, getting the caching benefit requires body-aware cache keys, which is a substantial change to how these caches work.
  • Load balancers & API gateways — AWS ALB/API Gateway, Azure Application Gateway, GCP Load Balancing, Kong, Apigee. These frequently validate methods and need explicit allow-listing.
  • WAFs & security appliances — many block non-standard methods as a hardening default, so QUERY will be denied until rules are updated.
  • Caching proxies — Varnish, Squid, and the caches embedded in the CDNs above.
  • HTTP client libraries — curl, browser fetch, axios, Go net/http, Python requests/httpx, Java clients. Most accept arbitrary method strings, but some validate against a fixed set.
  • Application frameworks — Express, Rails, Django, Spring, ASP.NET, Laravel. Routing layers need to recognize and dispatch QUERY (Rails' Action Pack, for example, currently rejects it before your controller runs).
  • Observability & tooling — logging, metrics, tracing, load-testing, and API tooling all need to understand the method to report on it correctly.

History sets the expectation here. PATCH was standardized in 2010 (RFC 5789) and still took years to become universally supported across servers, proxies, and client libraries. And bodies on "read" requests have a rocky past — many intermediaries have long stripped or ignored a body on GET, which is exactly the ambiguity QUERY was created to avoid. Because Cloudflare and Akamai engineers co-authored the spec, some large edge providers may move faster than average, but the long tail of self-hosted proxies, corporate WAFs, and older client libraries will lag for a long time.

Should you use it yet?

QUERY is genuinely the right method for large or structured read operations — think complex search and filter endpoints, GraphQL-style queries, or geospatial lookups. But as of mid-2026 it's new enough that you can't rely on forms, older clients, proxies, or many frameworks handling it. If you control both ends (your own API and a modern client using fetch), it's worth experimenting with today; for public-facing APIs, keep a POST fallback for a while.

Sources & further reading

About Code with Node.js

This is a personal blog and reference point of a Node.js developer.

I write and explain how different Node and JavaScript aspects work, as well as research popular and cool packages, and of course fail time to time.