Using GraphQL Persisted Queries to Shrink Request Payloads
Learn how GraphQL persisted queries replace large query bodies with short hashes, reducing bandwidth and boosting HTTP caching, with a concrete Apollo Server/Client setup and verification steps.
25 Oct 2025, 08:57 UTC

Problem: Large GraphQL requests waste bandwidth
When a mobile or low‑bandwidth client sends a GraphQL query, the entire query string travels in the HTTP body. Even a modest query can be several hundred bytes, and complex operations easily exceed 1 KB. This adds latency, hurts battery life, and makes HTTP caching less effective because the cache key includes the full body.
Thesis: Persisted queries replace the query string with a short identifier
Persisted queries (also called Automatic Persisted Queries, APQ) let the client send only a hash that the server maps to the full query text. The server maintains a registry of known queries; the client sends the hash via a GET request, and the server looks up the query, executes it, and returns the data. Subsequent requests benefit from standard HTTP caching because the URL (including the hash) is small and stable.
Server side: Enable persisted query support in Apollo Server
Apollo Server 2+ includes built‑in support for persisted queries through the apollo-server-plugin-persisted-queries plugin. Install the plugin and add it to the server configuration.
# Run in your Node.js project directory
npm install apollo-server-plugin-persisted-queries
const { ApolloServer } = require('apollo-server');
const { PersistedQueryPlugin } = require('apollo-server-plugin-persisted-queries');
const server = new ApolloServer({
typeDefs, // your GraphQL schema
resolvers, // your resolver map
plugins: [PersistedQueryPlugin()],
});
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
The plugin creates an in‑memory registry the first time it sees a full query (sent via POST). It stores the SHA‑256 hash of the query string and associates it with the text. When a later request arrives with only the hash, the plugin looks it up; if the hash is unknown it either falls back to accepting a full POST (if fallbackToGet is enabled) or returns an error.
Client side: Configure Apollo Client to use persisted queries
Apollo Client provides a link that creates the hash and sends it via GET. Install the link package.
# In your client project
npm install @apollo/client/link/persisted-queries
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
const httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });
const persistedQueryLink = createPersistedQueryLink();
const client = new ApolloClient({
link: persistedQueryLink.concat(httpLink),
cache: new InMemoryCache(),
});
The link works as follows:
- It computes the SHA‑256 hash of the GraphQL document.
- It sends a GET request with the query parameters
extensions.persistedQuery.version=1andextensions.persistedQuery.sha256=<hash>. - If the server responds with an error indicating the hash is unknown, the link automatically retries the request as a POST containing the full query body, allowing the server to register the new hash for future use.
Worked example: What the network looks like
Assume a client sends a query named GetUser. After the first POST, the server has stored the hash a3f5c2…. Subsequent requests appear in the browser’s Network tab as:
GET https://api.example.com/graphql?extensions.persistedQuery.version=1&extensions.persistedQuery.sha256=a3f5c2…
Notice there is no large query string in the request body; the payload is only a few dozen bytes. The response contains the usual GraphQL JSON data.
Trade‑off and limitation: Keeping the registry in sync
The main operational cost is ensuring that the server’s registry contains hashes for all queries that clients might send. If a client is deployed with a new or altered query before the server has registered its hash, the client will either:
- Fall back to sending the full query (extra bandwidth on that request), or
- Receive an error if the server is configured to reject unknown hashes.
Whitespace changes, even a single space, produce a different hash, so any edit to a query requires a new registration step. Teams often automate this by generating hashes during the build process and pushing them to the server (e.g., via a CI step that calls an internal registry API).
Practical verification steps
- Open Chrome DevTools → Network, filter by XHR/fetch to your GraphQL endpoint.
- Look for GET requests with the
extensions.persistedQueryquery string and verify that theRequest Payloadsection is empty (or shows only tiny parameters). - In the Apollo Server console, you should see logs like
Persisted query hit: a3f5c2…when the hash is found, orPersisted query miss: a3f5c2…followed by a fallback POST when it is not. - Check the response size: the GET request should be significantly smaller than the original POST (you can compare the
Sizecolumn in the Network tab).
Actionable closing
If your GraphQL API serves mobile or bandwidth‑sensitive clients, persisted queries are a low‑effort way to cut request size and improve cacheability. Start by adding the Apollo Server plugin, configure the Apollo Client link, and monitor the Network tab for the small GET URLs. Automate hash generation in your CI pipeline to keep the server registry aligned with client releases, and you’ll see fewer bytes on the wire without changing your schema or resolvers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.