Persisted GraphQL Queries Explained: How They Improve Performance and Security
Published by Shivam Kumar Dubey
Introduction
GraphQL has become one of the most popular technologies for building modern APIs because it gives clients the flexibility to request exactly the data they need. Unlike traditional REST APIs, where the server defines the response structure, GraphQL allows clients to construct their own queries, making applications more efficient and reducing unnecessary data transfer.
However, this flexibility comes with its own set of challenges. Since clients can send virtually any valid query, GraphQL APIs are more susceptible to issues such as:
Large request payloads
Expensive and deeply nested queries
Increased server processing time
Security vulnerabilities
Difficulties with caching and CDN optimization
As applications scale and traffic grows, these challenges can significantly impact both performance and reliability.
To address these problems, many production-grade GraphQL implementations—including those built with Apollo and other enterprise GraphQL platforms—use Persisted GraphQL Queries (PGQ). Instead of sending the entire GraphQL query with every request, the client sends a unique hash that references a pre-approved query stored on the server. This approach reduces network overhead, improves caching, enhances security, and helps optimize API performance.
In this article, we'll explore what Persisted GraphQL Queries are, how they work behind the scenes, their advantages and limitations, and the scenarios where they can make a significant difference in real-world GraphQL applications.
What Is a Persisted GraphQL Query?
In a standard GraphQL request, the client sends the entire query to the server every time it needs data. Before executing the request, the server must parse, validate, and execute the query.
For example, consider the following GraphQL query:
query GetProduct($id: ID!) {
product(id: $id) {
id
name
pricing {
price {
gross {
amount
}
}
}
}
}
Even if this exact query is executed thousands of times, the client still sends the complete query text with every request. This increases the request payload and requires the server to repeatedly process the same query.
How Persisted Queries Work
Persisted GraphQL Queries take a different approach. Instead of sending the full query, the client sends a unique SHA-256 hash that represents a query already stored on the server.
For example:
{
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "3e7d0d5d..."
}
}
}
When the server receives this hash, it performs the following steps:
Looks up the hash in its persisted query store.
Retrieves the corresponding GraphQL query.
Validates the stored query (if necessary).
Executes the query and returns the response.
Since the actual query is already known to the server, there is no need to transmit the entire query string with every request.
Why Is This Better?
Using persisted queries offers several benefits:
Smaller request payloads, reducing network bandwidth usage.
Faster requests, especially on slower or mobile networks.
Improved security, as the server can reject unknown or unauthorized queries.
Better caching, since requests are identified by a consistent hash.
Reduced server overhead, because the server works with predefined queries instead of arbitrary client-supplied ones.
Traditional GraphQL vs. Persisted GraphQL Queries
| Traditional GraphQL | Persisted GraphQL Queries |
|---|---|
| Sends the complete GraphQL query with every request | Sends only a unique query hash |
| Larger request payloads | Smaller and more efficient payloads |
| Allows clients to send any valid query | Executes only pre-approved persisted queries |
| More difficult to cache | Easier to cache using the query hash |
| Higher risk of malicious or expensive queries | Better protection against arbitrary query execution |
In short, Persisted GraphQL Queries replace large, repetitive query strings with a compact identifier, making GraphQL APIs faster, more secure, and more efficient for production environments.
How Persisted GraphQL Queries Work
Persisted GraphQL Queries follow a simple yet efficient workflow. Instead of sending the complete GraphQL query with every request, the client and server communicate using a unique identifier (hash) for the query. This reduces the amount of data transferred over the network and allows the server to execute only pre-approved queries.
Step 1: Create the GraphQL Query
The process begins when a developer writes a GraphQL query that the application needs to execute.
For example:
query GetProduct($id: ID!) {
product(id: $id) {
id
name
pricing {
price {
gross {
amount
}
}
}
}
}
This query is typically defined in the frontend application and remains unchanged unless the application logic changes.
Step 2: Generate a SHA-256 Hash
A SHA-256 hash is generated from the query text. This hash acts as a unique fingerprint for the query.
For example:
Query│▼SHA-256 Hash3e7d0d5d4b7d...
Even a small change to the query produces a completely different hash, ensuring each persisted query has a unique identifier.
Step 3: Store the Query on the Server
The server maintains a mapping between the generated hash and the corresponding GraphQL query.
Persisted Query Store
Hash GraphQL Query
────────────────────────────────────────────────────────────────
3e7d0d5d4b7d... → query GetProduct { ... }
8ac53f17cd91... → mutation UpdateUser { ... }
f91a71bc8e24... → query GetOrders { ... }
This mapping may be stored in memory, a database, or generated during the application's build and deployment process.
Step 4: Client Sends Only the Hash
When the application needs to execute the query, it no longer sends the entire GraphQL query. Instead, it sends only the persisted query hash.
Example request:
{
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "3e7d0d5d4b7d..."
}
},
"variables": {
"id": "UHJvZHVjdDox"
}
}
Since the request contains only a small hash instead of a large query string, the payload size is significantly reduced.
Step 5: Server Retrieves and Executes the Query
When the server receives the request, it performs the following operations:
Reads the SHA-256 hash from the request.
Searches for the matching query in the persisted query store.
Retrieves the corresponding GraphQL query.
Executes the query with the provided variables.
Returns the requested data to the client.
If the hash does not exist, the server can reject the request or ask the client to send the full query, depending on the implementation.
Workflow Diagram
┌───────────────┐ │ Client │ └──────┬────────┘ │ │ Create GraphQL Query ▼ ┌──────────────────────┐ │ Generate SHA-256 Hash│ └──────┬───────────────┘ │ │ Send Hash + Variables ▼ ┌──────────────────────┐ │ GraphQL Server │ └──────┬───────────────┘ │ │ Lookup Hash ▼ ┌──────────────────────┐ │ Persisted Query Store│ └──────┬───────────────┘ │ │ Retrieve Stored Query ▼ ┌──────────────────────┐ │ Execute Query │ └──────┬───────────────┘ │ │ Return Response ▼ ┌──────────────────────┐ │ Client │ └──────────────────────┘
Why This Approach Is More Efficient
By sending only a compact hash instead of the complete query text, Persisted GraphQL Queries provide several performance benefits:
Reduced bandwidth usage, as request payloads are much smaller.
Lower network latency, especially on mobile or slow connections.
Faster request processing, since the server works with predefined queries.
Improved caching, because each request is identified by a consistent hash.
Enhanced security, as only known and approved queries are executed.
This combination of performance and security is why Persisted GraphQL Queries are widely used in production GraphQL applications.
Why Use Persisted GraphQL Queries?
Persisted GraphQL Queries are more than just a performance optimization—they help improve the overall efficiency, security, and scalability of GraphQL applications. By replacing lengthy query strings with a compact hash, they solve several common challenges faced by production APIs.
Let's explore the key benefits.
1. Smaller Network Requests
In a traditional GraphQL request, the client sends the complete query every time it communicates with the server. For complex applications, these queries can easily be several kilobytes in size.
With Persisted Queries, the client sends only a SHA-256 hash that uniquely identifies the stored query.
Traditional Request
Persisted Query Request
Client│├── 64-byte SHA-256 Hash▼Server
This significantly reduces the amount of data transferred over the network.
Benefits
Smaller request payloads
Faster API requests, especially on mobile networks
Reduced bandwidth consumption
Lower network latency
Improved overall application responsiveness
2. Enhanced Security
One of the biggest advantages of Persisted GraphQL Queries is improved API security.
In a standard GraphQL API, clients are free to send almost any valid query. While this flexibility is one of GraphQL's strengths, it also increases the attack surface.
For example, an attacker may attempt to execute:
Deeply nested queries
Expensive database operations
Large introspection queries
Complex queries that consume excessive server resources
Denial-of-Service (DoS) attacks using computationally expensive requests
With Persisted Queries enabled, the server executes only pre-approved and registered queries.
If a request contains an unknown hash, the server can immediately reject it without executing any query.
Security Benefits
Prevents execution of arbitrary queries
Reduces the risk of malicious GraphQL requests
Limits expensive database operations
Helps protect against query-based DoS attacks
Gives developers greater control over API operations
Note: Persisted Queries improve security, but they should be used alongside other best practices such as authentication, authorization, query depth limits, complexity analysis, and rate limiting.
3. Improved Caching
Caching is one of the challenges with GraphQL because different clients can generate different query structures for similar data.
Persisted Queries solve this problem by assigning every approved query a unique and consistent hash.
Since the request identifier remains the same every time, caching becomes much more efficient.
Request│▼SHA-256 Hash│▼CDN / Proxy Cache│▼Cached Response
This allows CDNs, reverse proxies, and API gateways to identify repeated requests more effectively.
Benefits
Better CDN caching
Improved cache hit rates
Reduced response times
Lower backend traffic
Improved scalability under heavy load
4. Reduced Server Processing
Every standard GraphQL request requires the server to perform several steps before returning a response.
Receive Query│▼Parse Query│▼Validate Schema│▼Build Execution Plan│▼Execute Resolvers│▼Return Response
When the same query is executed thousands of times, the server repeatedly performs much of this work.
With Persisted Queries, the server works with predefined queries instead of processing arbitrary client-supplied query text. This reduces repeated overhead and allows the server to handle requests more efficiently.
Benefits
Less processing overhead
Faster request handling
Improved throughput
Better CPU utilization
More efficient handling of high-traffic applications
Summary of Benefits
| Benefit | Description |
|---|---|
| Smaller Requests | Only a hash is sent instead of the full GraphQL query, reducing payload size and bandwidth usage. |
| Enhanced Security | Prevents execution of unknown or unauthorized GraphQL queries. |
| Improved Caching | Consistent query hashes make CDN and proxy caching more effective. |
| Reduced Server Load | Minimizes repeated query processing, improving performance and scalability. |
Persisted GraphQL Queries are particularly valuable for production applications, mobile apps, high-traffic APIs, and large-scale GraphQL services, where even small improvements in performance and security can have a significant impact.
Example Workflow: Persisted GraphQL Queries in an E-commerce Application
To better understand how Persisted GraphQL Queries work, let's consider a real-world example of an e-commerce application.
Imagine you're building an online shopping platform. Every time a user visits a product page, the frontend needs to fetch the same set of information from the server, such as:
Product name
Product price
Product images
Stock availability
In a traditional GraphQL setup, the frontend sends the complete GraphQL query every time a user opens the product page.
query GetProduct($id: ID!) {
product(id: $id) {
id
name
images {
url
}
pricing {
price {
gross {
amount
}
}
}
quantityAvailable
}
}
If thousands of users visit the same product page every day, this entire query is transmitted over the network for every request, even though the query itself never changes.
Using Persisted Queries
With Persisted GraphQL Queries, the query is registered on the server beforehand. During runtime, the frontend sends only the query's unique SHA-256 hash instead of the complete query.
3e7d0d5d4b7d8d2c...
The request may look like this:
{
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "3e7d0d5d4b7d8d2c..."
}
},
"variables": {
"id": "UHJvZHVjdDox"
}
}
When the GraphQL server receives this request, it performs the following steps:
Reads the persisted query hash.
Searches for the corresponding query in the persisted query store.
Retrieves the stored GraphQL query.
Executes the query using the provided variables.
Returns the requested product data to the client.
Since the server already knows which query is associated with the hash, there is no need to transmit or parse the full query each time.
Workflow Illustration
User Opens Product Page│▼Frontend Sends SHA-256 Hash│▼GraphQL Server│▼Lookup Hash in Persisted Query Store│▼Retrieve Stored Query│▼Execute Query with Variables│▼Return Product Details
Why This Matters
This approach provides several advantages, especially for applications that receive a large number of repeated requests:
Smaller request payloads reduce bandwidth usage.
Faster request processing improves page load times.
The server executes only pre-approved queries, enhancing security.
Repeated requests become easier to cache using the query hash.
Reduced parsing and validation overhead allows the server to handle more traffic efficiently.
This is why Persisted GraphQL Queries are widely adopted in production applications such as e-commerce platforms, mobile apps, and other high-traffic GraphQL services, where the same queries are executed repeatedly.
When Should You Use Persisted GraphQL Queries?
Persisted GraphQL Queries are not mandatory for every GraphQL application, but they become increasingly valuable as your application grows in complexity, traffic, and security requirements.
If your API serves the same GraphQL operations repeatedly, persisted queries can significantly improve performance by reducing request payloads, lowering server overhead, and preventing the execution of arbitrary queries.
Below are some common scenarios where Persisted GraphQL Queries provide the greatest benefits.
1. E-commerce Platforms
Online stores frequently execute the same GraphQL queries for operations such as:
Product details
Category listings
Shopping cart
User profile
Order history
Since these queries are requested thousands of times every day, replacing the full query with a hash reduces bandwidth usage and improves response times.
Examples:
Saleor
Shopify
Magento GraphQL implementations
2. Mobile Applications
Mobile applications often operate on slower or unstable network connections where every byte matters.
Persisted Queries reduce the amount of data transmitted between the mobile app and the server, resulting in:
Faster API requests
Lower data usage
Improved performance on slow networks
Better battery efficiency due to reduced network activity
This is particularly beneficial for Android and iOS applications that frequently communicate with GraphQL backends.
3. Large GraphQL APIs
As GraphQL APIs grow, they typically expose hundreds of queries and mutations.
Allowing clients to send arbitrary queries can increase server load and make performance optimization more difficult.
Persisted Queries ensure that only approved operations are executed, making the API more predictable, secure, and easier to optimize.
4. Public APIs
Public GraphQL APIs are accessible to external developers and, in some cases, anonymous users.
Without proper controls, attackers may send expensive or malicious GraphQL queries that consume excessive server resources.
Persisted Queries help mitigate this risk by allowing the server to execute only registered queries while rejecting unknown or unauthorized requests.
5. High-Traffic Applications
Applications serving thousands—or even millions—of GraphQL requests each day benefit the most from Persisted Queries.
In high-traffic environments, even a small reduction in request size and processing time can lead to noticeable improvements in:
API response times
Server resource utilization
Network bandwidth consumption
Infrastructure costs
Overall scalability
For systems handling thousands of GraphQL requests every minute, these optimizations can make a significant difference in performance and reliability.
When You May Not Need Persisted Queries
Persisted Queries may not be necessary if:
You're building a small internal tool with only a few users.
Your GraphQL API is used primarily for development or experimentation.
Clients frequently generate dynamic, ad-hoc queries that cannot be registered in advance.
Performance and bandwidth are not significant concerns.
In these cases, the additional setup and management of persisted queries may outweigh their benefits.
Summary
Persisted GraphQL Queries are particularly valuable when your application:
Handles a high volume of repeated GraphQL requests
Needs to optimize network performance
Requires stronger API security
Wants to improve caching efficiency
Operates at production scale
While smaller projects may not immediately benefit from persisted queries, they become an essential optimization for production-grade GraphQL applications where performance, scalability, and security are top priorities.
Advantages of Persisted GraphQL Queries
Persisted GraphQL Queries offer several benefits that make them an excellent choice for production environments. By replacing full GraphQL queries with unique hashes, they improve both the performance and security of your API.
Smaller Request Size
Instead of transmitting the complete GraphQL query with every request, the client sends only a small SHA-256 hash. This significantly reduces the size of network requests, especially for large and complex queries.
Faster Response Times
Smaller payloads mean less data travels between the client and the server. As a result, requests are processed more quickly, leading to improved response times and a better user experience.
Reduced Bandwidth Usage
Since only a compact hash is transmitted, Persisted Queries consume much less bandwidth. This is particularly beneficial for mobile applications and users on slower network connections.
Better CDN and Proxy Caching
Every persisted query has a unique and consistent identifier. This makes it easier for CDNs, reverse proxies, and API gateways to cache requests efficiently, reducing unnecessary traffic to the backend.
Enhanced API Security
With Persisted Queries enabled, the server executes only pre-approved GraphQL operations. Unknown or unauthorized queries can be rejected immediately, reducing the risk of malicious or resource-intensive requests.
Lower Server Processing Overhead
Traditional GraphQL requests require the server to repeatedly parse, validate, and prepare execution plans for incoming queries. Persisted Queries reduce this repeated work by relying on predefined operations, allowing the server to process requests more efficiently.
Predictable GraphQL Operations
Because only registered queries are executed, API behavior becomes more predictable. This makes monitoring, debugging, performance optimization, and capacity planning much easier for development teams.
Limitations of Persisted GraphQL Queries
Although Persisted Queries provide many advantages, they also introduce a few trade-offs that should be considered before implementation.
Query Registration
Before a query can be executed, it must first be registered with the server. This requires an additional process during development or deployment.
Increased Development Complexity
Maintaining a persisted query registry, synchronizing client and server updates, and handling new query versions adds some complexity to the development workflow.
Less Flexible for Dynamic Queries
Applications that frequently generate dynamic or user-defined GraphQL queries may not benefit as much from Persisted Queries, since every new query must be registered before it can be executed.
Despite these limitations, the advantages typically outweigh the additional complexity for production-grade GraphQL applications where performance, scalability, and security are important.
Best Practices
To get the most out of Persisted GraphQL Queries, consider following these best practices:
Disable GraphQL Introspection in Production
Unless your API specifically requires schema introspection, disable it in production environments to reduce the amount of information exposed to potential attackers.
Combine with Authentication and Authorization
Persisted Queries are not a replacement for access control. Always secure your API with proper authentication and authorization mechanisms to ensure users can access only the resources they are permitted to use.
Apply Query Depth and Complexity Limits
Even when using Persisted Queries, enforce query depth and complexity limits to protect your server from expensive operations and unexpected performance issues.
Monitor API Usage
Log persisted query usage, monitor failed hash lookups, and track rejected requests. These metrics can help identify misconfigurations, outdated clients, or suspicious activity.
Version Your Persisted Queries
As your application evolves, queries may change. Maintaining a versioning strategy ensures backward compatibility and allows clients to migrate smoothly to newer query definitions.
Conclusion
Persisted GraphQL Queries are a powerful optimization for modern GraphQL applications. By replacing full query strings with pre-approved hashes, they reduce request payload sizes, improve API performance, enhance security, and enable more efficient caching.
Although implementing Persisted Queries requires some additional setup and maintenance, the long-term benefits often outweigh the initial effort—especially for applications with high traffic or complex GraphQL operations.
If you're building a production-ready GraphQL API, implementing Persisted Queries early in your architecture can help create a faster, more secure, and more scalable system as your application grows.
Frequently Asked Questions (FAQs)
1. Are Persisted GraphQL Queries mandatory?
No. GraphQL works perfectly without Persisted Queries. However, they are a valuable optimization for applications that prioritize performance, scalability, and security.
2. Do Persisted Queries improve security?
Yes. Persisted Queries prevent the execution of arbitrary GraphQL operations by allowing only pre-registered queries. When combined with authentication, authorization, rate limiting, and query complexity analysis, they significantly strengthen API security.
3. Can REST APIs use Persisted Queries?
Not in the same way. REST APIs already expose fixed endpoints, so the concept of persisting arbitrary queries is generally unnecessary. Persisted Queries are specifically designed to address the flexibility of GraphQL.
4. Do Persisted Queries improve performance?
Yes. They reduce network payload sizes, lower bandwidth usage, improve caching efficiency, and reduce the repeated processing required for identical GraphQL operations, making them especially beneficial for high-traffic applications.
5. Are Persisted Queries suitable for every GraphQL application?
Not always. Smaller internal tools or applications that rely heavily on dynamic, user-generated queries may not gain significant benefits. Persisted Queries are most effective for production systems where the same operations are executed repeatedly.
6. Which applications benefit the most from Persisted Queries?
Persisted Queries are particularly valuable for:
E-commerce platforms
Mobile applications
Enterprise GraphQL APIs
Public GraphQL services
SaaS applications
High-traffic production systems
In these scenarios, they help improve performance, reduce infrastructure costs, and provide stronger protection against malicious or inefficient queries.
If you found this article helpful, explore more backend engineering topics such as GraphQL security, Django optimization, Celery task processing, Redis caching, and production deployment.


Comments
Post a Comment