Diagnosing Persisted Query Failures in Apollo GraphQL: A Step‑by‑Step Guide
When Apollo Client returns PersistedQueryNotFound or PersistedQueryNotSupported, the server can’t process the query ID. This guide walks through the common causes, diagnostic steps, and fixes to get your persisted queries working again.
03 Mar 2026, 20:09 UTC

Problem Overview
When an Apollo Client request returns a PersistedQueryNotFound or PersistedQueryNotSupported error, the client has sent a persisted query ID that the server cannot process. The error can surface after a deployment, a server restart, or a client bundle rebuild. It typically manifests as a GraphQL error in the response body, sometimes accompanied by a 404 or 500 status code.
Recognizable Condition
Typical symptoms:
- The client logs a GraphQL error with
PersistedQueryNotFoundorPersistedQueryNotSupported. - The network tab shows a POST to
/graphqlwith the headerx-apollo-persisted-query-idbut noqueryfield. - The server logs indicate
PersistedQueryNotFoundor that persisted queries are disabled. - Occasional 404/500 responses appear when the client falls back to a full query.
Cause & Diagnostic Table
| Cause | Diagnostic Check | Typical Fix |
|---|---|---|
| Server lacks persisted query cache entry | Confirm server logs show PersistedQueryNotFound for the hash. | Reload persisted queries on startup or persist cache to disk/Redis. |
| Persisted queries disabled in server config | Check persistedQueries option in ApolloServer constructor. | Enable persisted queries or add persistedQueries: { cache: new InMemoryLRUCache() }. |
| Client and server use different hash algorithms | Verify both use SHA‑256 of the query string. | Align hash strategy or upgrade both libraries to the same major version. |
| Client bundle missing persistedQueries plugin | Inspect build output for persistedQueries plugin registration. | Add persistedQueries plugin to client build. |
CORS stripping x-apollo-persisted-query-id | Use devtools to confirm header is sent and not removed by proxy. | Configure CORS to allow custom headers or adjust proxy settings. |
| Server restart cleared in‑memory cache | Check if persisted queries are loaded on startup. | Persist queries to a shared store (Redis, file) and load them on boot. |
| Version mismatch between client and server | Confirm both use compatible Apollo packages (e.g., @apollo/server 4.x). | Upgrade or downgrade to matching major versions. |
Ordered Diagnostic Checklist
- Confirm the client is sending the persisted query ID.
# In browser devtools, Network tab Request Headers: x-apollo-persisted-query-id: <hash>Run the client with
networkInterface: { log: true }to print the outgoing headers. If the header is missing, the client isn’t using persisted queries. - Verify server accepts persisted queries.
const server = new ApolloServer({ typeDefs, resolvers, persistedQueries: { cache: new InMemoryLRUCache() }, // required });Check the server startup logs for "Persisted queries enabled". If absent, add the
persistedQueriesoption. - Test the persisted query ID manually.
curl -X POST \ https://api.example.com/graphql \ -H "Content-Type: application/json" \ -d '{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"<hash>"}}}'Expect a 200 response with data. A 404 indicates the server doesn’t recognize the hash.
- Check server logs for cache hits.
# In server code server.setPlugin({ requestDidStart: () => ({ willSendResponse: ({ request, response }) => { console.log(request.extensions?.persistedQuery?.found); }, }), });Logs should show
trueforfound. Iffalse, the cache is missing the entry. - Validate hash algorithm consistency.
// Both client and server const hash = crypto.createHash('sha256').update(queryString).digest('hex');Ensure no whitespace or formatting changes alter the string before hashing.
- Inspect CORS configuration.
app.use(cors({ allowedHeaders: ['Content-Type', 'x-apollo-persisted-query-id'], }));Missing
x-apollo-persisted-query-idinallowedHeaderswill cause the header to be stripped. - Check shared cache across instances.
const cache = new RedisCache({ client: redisClient, namespace: 'apollo:persisted-queries', });Ensure all server instances point to the same Redis namespace.
- Confirm fallback path works.
// Temporarily disable persisted queries const server = new ApolloServer({ typeDefs, resolvers, persistedQueries: false, });Send the same request; if it succeeds, the persisted query mechanism is the culprit.
Fixes Tied to Findings
- Reload persisted queries on startup. Add a script that reads the persisted query file and populates the cache before the server starts.
- Persist cache to disk or Redis. Use
RedisCacheorFileCachefrom@apollo/server. - Align client and server versions. Run
npm ls @apollo/client @apollo/serverand bump to matching major releases. - Enable custom header in CORS. Add
x-apollo-persisted-query-idtoallowedHeaders. - Verify build includes persistedQueries plugin. Check the Webpack/Rollup config for
persistedQueries.
Escalation Criteria
If after following the checklist the error persists:
- Check network logs for proxy or CDN stripping the header.
- Review server logs for permission issues accessing the persisted query store.
- Enable
debugmode in Apollo Server and capture the full error stack. - Raise an issue on the Apollo GitHub repository with reproducible steps and the minimal config that triggers the failure.
Practical Verification
After applying a fix, run a simple end‑to‑end test:
- Build the client bundle.
- Restart the server.
- Open the application and trigger the query.
- Verify the network request contains
x-apollo-persisted-query-idand receives a 200 response. - Check server logs for
PersistedQueryFoundentry.
If all steps succeed, the persisted query flow is restored.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.