Join us for GraphQL Summit, October 10-12 in San Diego. Use promo code ODYSSEY for $400 off your pass.
Docs
Launch GraphOS Studio

Persisted queries

Secure your graph while minimizing request latency


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

Hashed queries are also sent by default using HTTP GET instead of the default POST, making them easier to cache in your edge network.

Differences between persisted queries and APQs

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

With APQs, 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 Apollo client receives this error, it automatically retries the operation with the full operation string.

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

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

Implementation steps

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

We recommend you follow this order while implementing:

Implementation StepRequired for PQs?Required for APQs?
1. Generate the operation manifest--
2. Publish the operation manifest to a PQL--
3. Enable persisted queries on the client when it makes requests

The rest of this article details these steps.

Persisted queries also require you to create and link a PQL, and to configure your to receive persisted query requests. This document only describes the steps that need to be taken by the client to create a manifest of the client's 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 APQs with the following versions of Apollo Kotlin, Apollo Server, and Apollo :

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

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

Persisted queries is currently in preview and has the following requirements:

1. Generate operation manifest

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

The manifest acts as a safelist of trusted operations the Apollo Router can check incoming requests against. To generate the manifest, set operationManifestFormat to "persistedQueryManifest" in your Gradle script:

// build.gradle.kts
apollo {
service("api") {
packageName.set("com.example")
// Enable generation of the operation manifest
operationManifestFormat.set("persistedQueryManifest")
}
}

The manifest is generated during code generation. This happens automatically every time you build your project or you can trigger it manually by executing the generateApolloSources Gradle task.

The manifest is generated in build/generated/manifest/apollo/$serviceName/persistedQueryManifest.json, where $serviceName is "api" here. The resulting manifest looks something like this:

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

2. Publish operation manifest

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

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

After you generate an operation manifest, you publish it to your PQL 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 PQL is linked to.
    • Graph refs 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 Apollo client tools. The command can also support manifests generated by the Relay compiler by adding the --manifest-format relay . Your CLI version must be 0.19.0 or later to use this argument.

The persisted-queries publish command does the following:

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

    • Publishing a manifest to a PQL 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 PQL, the entire publish command fails with an error.
  2. Updates any other s that the PQL is applied to so that s 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 s 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 graph's data in , and are appropriate for sharing with trusted third party client developers who should be allowed to publish s to your graph's PQL but should not otherwise have access to your graph.

Test operations

You can send some test s 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 (https://go.apollo.dev/elv2)
2023-05-11T15:32:30.684480Z INFO Anonymous usage data is gathered to inform Apollo product development. See https://go.apollo.dev/o/privacy for details.
2023-05-11T15:32:31.507085Z INFO Health check endpoint exposed at http://127.0.0.1:8088/health
2023-05-11T15:32:31.507823Z INFO GraphQL endpoint exposed at http://127.0.0.1:4000/ 🚀

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

curl http://localhost:4000 -X POST --json \
'{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"dc67510fb4289672bea757e862d6b00e83db5d3cbbcfb15260601b6f29bb2b8f"}}}'

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

3. Enable persisted queries on ApolloClient

Once you've configured your code generation to include IDs, you can update your client to query by operation ID rather than the full operation string. This configuration is the same whether you're using APQs or persisted queries. Call autoPersistedQueries() on your ApolloClient.Builder:

val apolloClient = ApolloClient.Builder()
.serverUrl("https://...")
.autoPersistedQueries()
.build()

Once APQs are enabled on your ApolloClient, hashed queries are sent by default.

You may want to disable automatic persisted queries for certain queries, for instance to avoid any caching when the data is updated often. To do that, set enableAutoPersistedQueries to false on the ApolloCall:

apolloClient.query(myQuery).enableAutoPersistedQueries(false).toFlow()

Generating custom IDs for persisted queries

By default, Apollo uses Sha256 hashing algorithm to generate an ID for the query. To provide custom ID generation logic, use the operationIdGenerator option. It accepts an instance that implements the OperationIdGenerator interface (com.apollographql.apollo3.compiler.OperationIdGenerator) as the input. You can use this option to either specify a different hashing algorithm or to fetch the persisted query ID from a different location, for example, from a service or a CLI.

Example Md5 hash generator:

apollo {
service("service") {
operationIdGenerator.set(object : com.apollographql.apollo3.compiler.OperationIdGenerator {
override val version = "my-md5-version1"
override fun apply(operationDocument: String, operationName: String): String {
return operationDocument.md5()
}
})
}
}
import com.apollographql.apollo3.compiler.OperationIdGenerator
apollo {
service("service") {
operationIdGenerator = new OperationIdGenerator() {
String apply(String operationDocument, String operationName) {
return operationDocument.md5()
}
/**
* Use this version override to indicate an update to the implementation.
* This forces gradle to recompile models.
*/
String version = "my-md5-v1"
}
}
}

ID generator versioning

The result of the ID generator is cached. The cache is not updated when the implementation of the ID Generator changes. To indicate an update to the implementation of the ID Generator, change the version override as shown in the above example.

Previous
@defer support (experimental)
Next
Introduction
Edit on GitHubEditForumsDiscord