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

  1. 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)
  2. Verify the hash was computed over the exact query string (whitespace-sensitive) with sha256, not md5 or a normalized variant
  3. Re-register the persisted query (issue the query+hash pair) after any DropAll/restore of the data
  4. 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

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


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/debc961f5365a95a. Report an issue: GitHub.