Accept POST for everything and add GET only for read queries you want cached, ideally through persisted documents. The GraphQL project says servers must handle POST for queries and mutations and may accept GET for queries. A GET request puts the query in the URL, which is easier to cache and also easier to log, share and replay.
What the standard guidance says
The GraphQL project's page on serving over HTTP states that a GraphQL HTTP server must handle the POST method for query and mutation operations, and may also accept GET for query operations. For GET, the document goes in the query parameter of the query string, with variables as a JSON encoded string in another parameter.
It adds that GET may be used only for query operations. A mutation must use POST. It also notes that URL length limits in browsers and CDNs can be a problem for big queries, and that a common answer is to store documents on the server and send a short identifier, using persisted documents, automatic persisted queries or trusted documents.
Where the security trade offs come from
The following points are general web security reasoning rather than claims from the pages cited:
- URLs travel widely. Query strings can appear in proxy logs, analytics, browser history and the referrer header. If a query or its variables contain anything sensitive, GET exposes it to those places.
- GET should have no side effects. Browsers and caches treat GET as safe to repeat. That is why mutations must not be accepted over GET. Check that your server really rejects a mutation sent as GET.
- Caches need care. A shared cache that stores responses for authenticated queries without varying by user can serve one person's data to another. Cache only responses meant to be public, and set cache control headers deliberately.
- Cross site requests. GET requests can be triggered from other sites by a simple link or image tag. That is another reason read queries should be free of side effects and authorization should not depend on ambient state alone.
Comparison
| Aspect | POST | GET for queries |
|---|---|---|
| Allowed operations | Queries and mutations | Queries only |
| Variables location | Request body | URL parameter |
| Caching | Harder by default | Easier with CDNs |
| Log exposure | Body usually not logged | URL usually logged |
| Size limits | Large bodies fine | URL length limits |
Steps to decide
- List which clients call the API and whether any benefit from edge caching.
- If none do, disable GET and accept POST only. Simpler surface, fewer surprises.
- If some do, enable GET for queries and pair it with persisted documents so URLs carry a short ID, not query text and sensitive variables.
- Confirm mutations over GET are rejected with an error, and write a test for it.
- Set cache control headers on authenticated responses so shared caches do not store them.
- Scrub query strings from logs where variables might hold personal data, or avoid putting such variables in URLs at all.
- Require the expected content type on POST requests and reject others. The standard guidance describes
application/jsonfor the request body.
What this does not solve
Choosing the method does not fix authorization, depth abuse or batching. It only changes how the request travels. Keep your other controls in place. If you rely on cookies for authentication, review how your server handles requests from other origins as well.
Try it yourself
Click through it at your own pace, or open it full screen.
Look at how a dynamic scan exercises a running app, and think about testing mutations over GET.
Quick check
Can a mutation be sent with GET?
It should not be. The GraphQL guidance says GET may be used only for queries, so mutations must use POST. Test that your server rejects the other case.
Why do persisted documents come up with GET?
URLs have length limits, so short document identifiers replace long query text. That also keeps variables and query shapes out of long URLs.
Safeguard scans your dependencies and SBOMs for known vulnerabilities, including the HTTP and GraphQL libraries behind your API. A free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.