Launch GraphOS Studio

Persisted Queries

Secure your graph while minimizing request latency

Apollo supports two separate but related features called automatic persisted queries () and persisted queries. With both features, clients can execute a by sending an operation's ID instead of the entire operation string. An operation's ID is a hash of the full operation string. by ID can significantly reduce latency and bandwidth usage for large operation strings.

Differences between persisted queries and APQ

The feature requires to be registered in a persisted query list (PQL). This allows the to act as an safelist made by your first-party apps. As such, is a security feature as much as a performance one.

With , if the server can't find the ID the client provides, the server returns an error indicating that it needs the full operation string. If an receives this error, it automatically retries the operation with the full operation string.

If you only want to improve request latency and bandwidth usage, addresses your use case. If you also want to secure your with safelisting, you should register operations in a .

For more details on differences between and , see the GraphOS persisted queries documentation.

Implementation steps

Both and require you to configure code generation and how your client makes requests. If you intend to use persisted queries for safelisting, you also need to generate an manifest.

We recommend you follow this order while implementing:

Implementation StepRequired for PQs?Required for APQs?
1. Configure generated operation models
2. Generate the operation manifest--
3. Publish the operation manifest--
4. Enable persisted queries on the client when it makes requests

The rest of this article details these steps.

also require you to create and link a , and to configure your to receive requests. This only describes the steps that need to be taken by the client to create a manifest of the client's and send persisted query requests. For more information on the other configuration aspects of persisted queries, see the GraphOS persisted queries documentation.

0. Requirements

You can use with the following versions of , , and :

  • (v1.0.0+)
  • Apollo Server (v1.0.0+)
  • Apollo Router (v0.1.0+)

Note: You can use either or for . They don't need to be used together.

Using for safelisting has the following requirements:

1. Configure generated operation models

Both and require your code generation to include IDs. You can configure this in your code generation configuration's options. Specifically, set the operationDocumentFormat array to definition, operationId, or both definition and operationId.

  • To use , you must include both the definition and operationId.
  • For , you only need the operationId.
"options": {
"operationDocumentFormat" : [

2. Generate operation manifest

This step is only required for implementing safelisting with . It is not required for .

An manifest acts as a safelist of trusted operations the Apollo Router can check incoming requests against. You can generate manifests by adding the operationManifest option to your ApolloCodegenConfiguration JSON file like so:

"operationManifest" : {
"generateManifestOnCodeGeneration" : false,
"path" : "/operation/identifiers/path",
"version" : "persistedQueries"

Once these options are configured you can run the generate-operation-manifest in order to generate your manifest. If you have the generateManifestOnCodeGeneration flag set to true your manifest will also generate everytime you run the generate command.

The resulting manifest for persistedQueries looks like this:

"format": "apollo-persisted-query-manifest",
"version": 1,
"operations": [
"id": "e0321f6b438bb42c022f633d38c19549dea9a2d55c908f64c5c6cb8403442fef",
"body": "query GetItem { thing { __typename } }",
"name": "GetItem",
"type": "query"

To automatically update the manifest for each new app release, you can include the generate or generate-operation-manifest command in your CI/CD pipeline.

3. Publish operation manifest

This step is only required for implementing safelisting with . It is not required for .


Ensure your version is 0.17.2 or later. Previous versions of don't support publishing to a . Download the latest version.

After you generate an operation manifest, you publish it to your with the Rover CLI like so:

Example command
rover persisted-queries publish my-graph@my-variant \
--manifest ./persisted-query-manifest.json
  • The my-graph@my-variant is the graph ref of any the is linked to.
    • have the format graph-id@variant-name.
  • Use the --manifest option to provide the path to the manifest you want to publish.


The persisted-queries publish command assumes manifests are in the format generated by tools. The command can also support manifests generated by the Relay compiler by adding the --manifest-format relay . Your version must be 0.19.0 or later to use this argument.

The persisted-queries publish command does the following:

  1. Publishes all in the provided manifest file to the linked to the specified , or to the specified PQL.

    • Publishing a manifest to a is additive. Any existing entries in the PQL remain.
    • If you publish an with the same id but different details from an existing entry in the , the entire publish command fails with an error.
  2. Updates any other that the is applied to so that associated with those variants can fetch their updated PQL.

As with generating manifests, it's best to execute this command in your CI/CD pipeline to publish new as part of your app release process. The API key you supply to must have the role of Graph Admin or Persisted Query Publisher. Persisted Query Publisher is a special role designed for use with the rover persisted-queries publish command; API keys with this role have no other access to your 's data in , and are appropriate for sharing with trusted third party client developers who should be allowed to publish to your graph's but should not otherwise have access to your graph.

Test operations

You can send some test to test that you've successfully published your manifests:

First, start your -connected :

APOLLO_KEY="..." APOLLO_GRAPH_REF="..." ./router --config ./router.yaml
2023-05-11T15:32:30.684460Z INFO Apollo Router v1.18.1 // (c) Apollo Graph, Inc. // Licensed as ELv2 (
2023-05-11T15:32:30.684480Z INFO Anonymous usage data is gathered to inform Apollo product development. See for details.
2023-05-11T15:32:31.507085Z INFO Health check endpoint exposed at
2023-05-11T15:32:31.507823Z INFO GraphQL endpoint exposed at 🚀

Next, make a POST request with curl, like so:

curl http://localhost:4000 -X POST --json \

If your 's includes an with an ID that matches the value of the provided sha256Hash property, it executes the corresponding and returns its result.

4. Enable persisted queries on ApolloClient

Once you've configured your code generation to include IDs, you can update your client to by operation ID rather than the full operation string. This configuration is the same whether you're using or :

  • Initialize a custom NetworkTransport using RequestChainNetworkTransport with autoPersistQueries parameter set to true and an interceptorProvider that includes the AutomaticPersistedQueryInterceptor (such as DefaultInterceptorProvider).
  • Initialize your ApolloClient with the custom NetworkTransport that supports .
let store = ApolloStore(cache: InMemoryNormalizedCache())
let interceptorProvider = DefaultInterceptorProvider(store: store)
let networkTransport = RequestChainNetworkTransport(
interceptorProvider: interceptorProvider,
endpointURL: URL(string: "http://localhost:4000/graphql")!,
autoPersistQueries: true
let client = ApolloClient(networkTransport: networkTransport, store: store)

For more information on configuring your ApolloClient, NetworkTransport, InterceptorProvider, and the request chain, see the request pipeline documentation.

Sending APQ retries as GET requests

Note: only retries failed ID-based for , not .

By default, the sends retries as POST requests. In some cases, you may prefer or need to retry an using a GET request: for example, you may make requests to a CDN that has better performance with GETs.

To use GET for retry requests, set the useGETForPersistedQueryRetry on your RequestChainNetworkTransport to true.

In most cases, keeping the default option (false) suffices.

Custom Scalars
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy