dgraph-io/dgraph · error
PersistedQueryNotFound
Error message
PersistedQueryNotFound
What it means
In ProcessPersistedQuery, a request carrying only an extensions.persistedQuery.sha256Hash but no query string is resolved by looking the hash up in the stored persisted queries; if the lookup returns no rows, the server returns PersistedQueryNotFound. This is the standard Apollo persisted-query miss, indicating the client cached hash isn't stored on the server.
Source
Thrown at edgraph/graphql.go:92
return err
}
type shaQueryResponse struct {
Me []struct {
PersistedQuery string `json:"dgraph.graphql.p_query"`
} `json:"me"`
}
shaQueryRes := &shaQueryResponse{}
if len(storedQuery.Json) > 0 {
if err := json.Unmarshal(storedQuery.Json, shaQueryRes); err != nil {
return err
}
}
if len(shaQueryRes.Me) == 0 {
if query == "" {
return errors.New("PersistedQueryNotFound")
}
if match, err := hashMatches(query, sha256Hash); err != nil {
return err
} else if !match {
return errors.New("provided sha does not match query")
}
req = &Request{
req: &api.Request{
Mutations: []*api.Mutation{
{
Set: []*api.NQuad{
{
Subject: "_:a",
Predicate: "dgraph.graphql.p_query",
ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: join}},
},
{View on GitHub (pinned to 759e242be6)
Solutions
- Send the full query text along with the sha256Hash once — the server verifies the hash and registers the persisted query (this is the automatic Apollo retry path)
- Verify the hash was computed over the exact query string (whitespace-sensitive) with sha256, not md5 or a normalized variant
- Re-register the persisted query (issue the query+hash pair) after any DropAll/restore of the data
- Check the GraphQL persisted-query registration mutation result for errors on the initial request
Example fix
// before: hash-only request that misses
{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"abc..."}}}
// after: include query to self-register on miss
{"query":"query Me { me { name } }","extensions":{"persistedQuery":{"version":1,"sha256Hash":"<sha256(query)>"}}} Defensive patterns
Strategy: fallback
Try / catch
async function runPersisted(sha, buildQuery) {
try {
return await gql({ extensions: { persistedQuery: { version: 1, sha256Hash: sha } } })
} catch (e) {
if (String(e.message).includes('PersistedQueryNotFound')) {
// standard Apollo recovery: resend full query + hash to re-register
return gql({ query: buildQuery(), extensions: { persistedQuery: { version: 1, sha256Hash: sha } } })
}
throw e
}
} Prevention
- Configure Apollo PersistedQueryLink with automatic fallback (default behavior) — never disable useGETForHashedQueries/retry logic blindly
- Re-register persisted queries after DropAll/restore of the GraphQL data
- Use the same hashing normalization across all client versions
When it happens
Trigger: POST/GET with extensions {"persistedQuery":{"version":1,"sha256Hash":"..."}} and no `query` field, when the sha256Hash doesn't match any stored PersistedQuery node for that user.
Common situations: Client (Apollo Link PersistedQueries) sends hash from cache but server data was wiped/restored (e.g. after DropAll); hash registered in a different namespace or under a different user; server restarted with fresh data; query was never registered because the first request failed.
Related errors
- provided sha does not match query
- same sha returned %d queries
- Cycle detected: %s
- Missing fragment: %s
- Unavailable: Server not ready.
AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01).
Data as JSON: /api/errors/debc961f5365a95a.
Report an issue: GitHub.