In urql, caching is fully configurable via exchanges, and the default cacheExchange in urql offers a "Document Cache", which is usually enough for sites that heavily rely on static content. However as an app grows more complex it's likely that the data and state that urql manages, will also grow more complex and introduce interdependencies between data.

To solve this problem most GraphQL clients resort to caching data in a normalized format, similar to how data is often structured in Redux.

In urql, normalized caching is an opt-in feature, which is provided by the @urql/exchange-graphcache package, Graphcache for short.


The following pages introduce different features in Graphcache, which together make it a compelling alternative to the standard document cache that urql uses by default.

  • 🔁 Fully reactive, normalized caching. Graphcache stores data in a normalized data structure. Query, mutation and subscription results may update one another if they share data, and the app will rerender or refetch data accordingly. This often allows your app to make fewer API requests, since data may already be in the cache.
  • 💾 Custom cache resolvers Since all queries are fully resolved in the cache before and after they're sent, you can add custom resolvers that enable you to format data, implement pagination, or implement cache redirects.
  • 💭 Subscription and Mutation updates You can implement update functions that tell Graphcache how to update its data after a mutation has been executed, or whenever a subscription sends a new event. This allows the cache to reactively update itself without queries having to perform a refetch.
  • 🏃 Optimistic mutation updates When implemented, optimistic updates can provide the data that the GraphQL API is expected to send back before the request succeeds, which allows the app to instantly render an update while the GraphQL mutation is executed in the background.
  • 🧠 Opt-in schema awareness Graphcache also optionally accepts your entire schema, which allows it to resolve partial data before making a request to the GraphQL API, allowing an app to render everything that's cached before receiving all missing data. It also allows Graphcache to output more helpful warnings and to handle interfaces and enums correctly without heuristics.
  • 📡 Offline support Graphcache can persist and rehydrate its entire state, allowing an offline application to be built that is able to execute queries against the cache although the device is offline.
  • 🐛 Errors and warnings. All potential errors are documented with information on how you may be able to fix them.

Installation and Setup

We can add Graphcache by installing the @urql/exchange-graphcache package. Using the package won't increase your bundle size by as much as platforms like Bundlephobia may suggest, since it shares the dependency on wonka and @urql/core with the framework bindings package, e.g. urql or @urql/preact, that you're already using.

yarn add @urql/exchange-graphcache
# or
npm install --save @urql/exchange-graphcache

The package exports the cacheExchange which replaces the default cacheExchange in @urql/core. This new cacheExchange must be instantiated using some options, which are used to customise Graphcache as introduced in the "Features" section above. However, you can get started without passing any options.

import { createClient, dedupExchange, fetchExchange } from 'urql';
import { cacheExchange } from '@urql/exchange-graphcache';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [dedupExchange, cacheExchange({}), fetchExchange],

This will automatically enable normalized caching, and you may find that in a lot of cases, Graphcache already does what you'd expect it to do without any additional configuration. We'll explore how to customize and set up different parts of Graphcache on the following pages.

Read more about "Normalized Caching" on the next page.