Persisted queries are GraphQL queries that are created and stored on the Adobe Experience Manager (AEM) as a Cloud Service server. They can be requested with a GET request by client applications. The response of a GET request can be cached at the dispatcher and CDN layers, ultimately improving the performance of the requesting client application. This differs from standard GraphQL queries, which are executed using POST requests where the response cannot easily be cached.
Persisted Queries are recommended. See GraphQL Query Best Practices (Dispatcher) for details, and the related Dispatcher configuration.
The GraphiQL IDE is available in AEM for you to develop, test, and persist your GraphQL queries, before transferring to your production environment. For cases that need customization (for example, when customizing the cache) you can use the API; see the cURL example provided in How to persist a GraphQL query.
Persisted queries must always use the endpoint related to the appropriate Sites configuration; so they can use either, or both:
See Enable Content Fragment Functionality in Configuration Browser for more details.
The GraphQL Persisted Queries need to be enabled, for the appropriate Sites configuration.
For example, if there is a particular query called my-query
, which uses a model my-model
from the Sites configuration my-conf
:
my-conf
specific endpoint, and then the query will be saved as following:/conf/my-conf/settings/graphql/persistentQueries/my-query
global
endpoint, but then the query will be saved as following:/conf/global/settings/graphql/persistentQueries/my-query
These are two different queries - saved under different paths.
They just happen to use the same model - but via different endpoints.
It is recommended to persist queries on an AEM author environment initially and then transfer the query to your production AEM publish environment, for use by applications.
There are various methods of persisting queries, including:
The GraphiQL IDE is the preferred method for persisting queries. To persist a given query using the cURL command line tool:
Prepare the query by PUTing it to the new endpoint URL /graphql/persist.json/<config>/<persisted-label>
.
For example, create a persisted query:
$ curl -X PUT \
-H 'authorization: Basic YWRtaW46YWRtaW4=' \
-H "Content-Type: application/json" \
"http://localhost:4502/graphql/persist.json/wknd/plain-article-query" \
-d \
'{
articleList {
items{
_path
author
main {
json
}
}
}
}'
At this point, check the response.
For example, check for success:
{
"action": "create",
"configurationName": "wknd",
"name": "plain-article-query",
"shortPath": "/wknd/plain-article-query",
"path": "/conf/wknd/settings/graphql/persistentQueries/plain-article-query"
}
You can then request the persisted query by GETing the URL /graphql/execute.json/<shortPath>
.
For example, use the persisted query:
$ curl -X GET \
http://localhost:4502/graphql/execute.json/wknd/plain-article-query
Update a persisted query by POSTing to an already existing query path.
For example, use the persisted query:
$ curl -X POST \
-H 'authorization: Basic YWRtaW46YWRtaW4=' \
-H "Content-Type: application/json" \
"http://localhost:4502/graphql/persist.json/wknd/plain-article-query" \
-d \
'{
articleList {
items{
_path
author
main {
json
}
referencearticle {
_path
}
}
}
}'
Create a wrapped plain query.
For example:
$ curl -X PUT \
-H 'authorization: Basic YWRtaW46YWRtaW4=' \
-H "Content-Type: application/json" \
"http://localhost:4502/graphql/persist.json/wknd/plain-article-query-wrapped" \
-d \
'{ "query": "{articleList { items { _path author main { json } referencearticle { _path } } } }"}'
Create a wrapped plain query with cache control.
For example:
$ curl -X PUT \
-H 'authorization: Basic YWRtaW46YWRtaW4=' \
-H "Content-Type: application/json" \
"http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \
-d \
'{ "query": "{articleList { items { _path author main { json } referencearticle { _path } } } }", "cache-control": { "max-age": 300 }}'
Create a persisted query with parameters:
For example:
$ curl -X PUT \
-H 'authorization: Basic YWRtaW46YWRtaW4=' \
-H "Content-Type: application/json" \
"http://localhost:4502/graphql/persist.json/wknd/plain-article-query-parameters" \
-d \
'query GetAsGraphqlModelTestByPath($apath: String!, $withReference: Boolean = true) {
articleByPath(_path: $apath) {
item {
_path
author
main {
plaintext
}
referencearticle @include(if: $withReference) {
_path
}
}
}
}'
To execute a Persisted query, a client application makes a GET request using the following syntax:
GET <AEM_HOST>/graphql/execute.json/<PERSISTENT_PATH>
Where PERSISTENT_PATH
is a shortened path to where the Persisted query is saved.
For example wknd
is the configuration name and plain-article-query
is the name of the Persisted query. To execute the query:
$ curl -X GET \
https://publish-p123-e456.adobeaemcloud.com/graphql/execute.json/wknd/plain-article-query
Executing a query with parameters.
Query variables and values must be properly encoded when executing a Persisted query.
For example:
$ curl -X GET \
"https://publish-p123-e456.adobeaemcloud.com/graphql/execute.json/wknd/plain-article-query-parameters%3Bapath%3D%2Fcontent%2Fdam%2Fwknd%2Fen%2Fmagazine%2Falaska-adventure%2Falaskan-adventures%3BwithReference%3Dfalse
See using query variables for more details.
Query variables can be used with Persisted Queries. The query variables are appended to the request prefixed with a semicolon (;
) using the variable name and value. Multiple variables are separated by semicolons.
The pattern looks like the following:
<AEM_HOST>/graphql/execute.json/<PERSISTENT_QUERY_PATH>;variable1=value1;variable2=value2
For example the following query contains a variable activity
to filter a list based on an activity value:
query getAdventuresByActivity($activity: String!) {
adventureList (filter: {
adventureActivity: {
_expressions: [
{
value: $activity
}
]
}
}){
items {
_path
adventureTitle
adventurePrice
adventureTripLength
}
}
}
This query can be persisted under a path wknd/adventures-by-activity
. To call the Persisted query where activity=Camping
the request would look like this:
<AEM_HOST>/graphql/execute.json/wknd/adventures-by-activity%3Bactivity%3DCamping
Note that %3B
is the UTF-8 encoding for ;
and %3D
is the encoding for =
. The query variables and any special characters must be encoded properly for the Persisted query to execute.
Persisted queries are recommended as they can be cached at the Dispatcher and Content Delivery Network (CDN) layers, ultimately improving the performance of the requesting client application.
By default AEM will invalidate cache based on a Time To Live (TTL) definition. These TTLs can be defined by the following parameters. These parameters can be accessed by various means, with variations in the names according to the mechanism used:
Cache Type | HTTP header | cURL | OSGi Configuration | Cloud Manager |
---|---|---|---|---|
Browser | max-age |
cache-control : max-age |
cacheControlMaxAge |
graphqlCacheControl |
CDN | s-maxage |
surrogate-control : max-age |
surrogateControlMaxAge |
graphqlSurrogateControl |
CDN | stale-while-revalidate |
surrogate-control : stale-while-revalidate |
surrogateControlStaleWhileRevalidate |
graphqlStaleWhileRevalidate |
CDN | stale-if-error |
surrogate-control : stale-if-error |
surrogateControlStaleIfError |
graphqlStaleIfError |
For author instances the default values are:
max-age
: 60s-maxage
: 60stale-while-revalidate
: 86400stale-if-error
: 86400These:
cache-control
and/or surrogate-control
; for examples, see Managing Cache at the Persisted Query LevelFor publish instances the default values are:
max-age
: 60s-maxage
: 7200stale-while-revalidate
: 86400stale-if-error
: 86400These can be overwritten:
at the Persisted Query Level; this involves posting the query to AEM using cURL in your command line interface, and publishing the Persisted Query.
The GraphiQL IDE - see Saving Persisted Queries
This involves posting the query to AEM using cURL in your command line interface.
For an example of the PUT (create) method:
curl -u admin:admin -X PUT \
--url "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \
--header "Content-Type: application/json" \
--data '{ "query": "{articleList { items { _path author } } }", "cache-control": { "max-age": 300 }, "surrogate-control": {"max-age":600, "stale-while-revalidate":1000, "stale-if-error":1000} }'
For an example of the POST (update) method:
curl -u admin:admin -X POST \
--url "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \
--header "Content-Type: application/json" \
--data '{ "query": "{articleList { items { _path author } } }", "cache-control": { "max-age": 300 }, "surrogate-control": {"max-age":600, "stale-while-revalidate":1000, "stale-if-error":1000} }'
The cache-control
can be set at the creation time (PUT) or later on (for example, via a POST request for instance). The cache-control is optional when creating the persisted query, as AEM can provide the default value. See How to persist a GraphQL query, for an example of persisting a query using cURL.
Cloud Manager Environment Variables can be defined with Cloud Manager to define the required values:
Name | Value | Service Applied | Type |
---|---|---|---|
graphqlStaleIfError |
86400 | as appropriate | as appropriate |
graphqlSurrogateControl |
600 | as appropriate | as appropriate |
To manage the cache globally, you can configure the OSGi settings for the Persisted Query Service Configuration.
The OSGi configuration is only appropriate for publish instances. The configuration exists on author instances, but is ignored.
The default OSGi configuration for publish instances:
reads the Cloud Manager variables if available:
OSGi Configuration Property | reads this | Cloud Manager Variable |
---|---|---|
cacheControlMaxAge |
reads | graphqlCacheControl |
surrogateControlMaxAge |
reads | graphqlSurrogateControl |
surrogateControlStaleWhileRevalidate |
reads | graphqlStaleWhileRevalidate |
surrogateControlStaleIfError |
reads | graphqlStaleIfError |
and if not available, the OSGi configuration uses the default values for publish instances.
For use by an application, any special characters used when constructing query variables (i.e semicolons (;
), equal sign (=
), slashes /
) must be converted to use the corresponding UTF-8 encoding.
For example:
curl -X GET \ "https://publish-p123-e456.adobeaemcloud.com/graphql/execute.json/wknd/adventure-by-path%3BadventurePath%3D%2Fcontent%2Fdam%2Fwknd%2Fen%2Fadventures%2Fbali-surf-camp%2Fbali-surf-camp"
The URL can be broken down into the following parts:
URL Part | Description |
---|---|
/graphql/execute.json |
Persisted query endpoint |
/wknd/adventure-by-path |
Persisted query path |
%3B |
Encoding of ; |
adventurePath |
Query variable |
%3D |
Encoding of = |
%2F |
Encoding of / |
%2Fcontent%2Fdam... |
Encoded path to the Content fragment |
In plain text the request URI looks like the following:
/graphql/execute.json/wknd/adventure-by-path;adventurePath=/content/dam/wknd/en/adventures/bali-surf-camp/bali-surf-camp
To use a persisted query in a client app, the AEM headless client SDK should be used for JavaScript, Java, or NodeJS. The Headless Client SDK will automatically encode any query variables appropriately in the request.
Persisted queries should always be created on an AEM Author service and then published (replicated) to an AEM Publish service. Often, Persisted queries are created and tested on lower environments like local or Development environments. It is then necessary to promote Persisted queries to higher level environments, ultimately making them available on a production AEM Publish environment for client applications to consume.
Persisted queries can be built into AEM Packages. AEM Packages can then be downloaded and installed on different environments. AEM Packages can also be replicated from an AEM Author environment to AEM Publish environments.
To create a Package:
persistentQueries
folder beneath the configuration. For example for the wknd
configuration the full path will be /conf/wknd/settings/graphql/persistentQueries
.After the package has been built you can: