---
title: createWithCache
description: |
Creates utility functions to store data in cache with stale-while-revalidate support.
- Use `withCache.fetch` to simply fetch data from a third-party API.
Fetches data from a URL and caches the result according to the strategy provided.
When the response is not successful (e.g. status code >= 400), the caching is
skipped automatically and the returned `data` is `null`.
You can also prevent caching by using the `shouldCacheResponse` option and returning
`false` from the function you pass in. For example, you might want to fetch data from a
third-party GraphQL API but not cache the result if the GraphQL response body contains errors.
- Use the more advanced `withCache.run` to execute any asynchronous operation.
Utility function that executes asynchronous operations and caches the
result according to the strategy provided. Use this to do any type
of asynchronous operation where `withCache.fetch` is insufficient.
For example, when making multiple calls to a third-party API where the
result of all of them needs to be cached under the same cache key.
Whatever data is returned from the `fn` will be cached according
to the strategy provided.
> Note:
> To prevent caching the result you must throw an error. Otherwise, the result will be cached.
> For example, if you call `fetch` but the response is not successful (e.g. status code >= 400),
> you should throw an error. Otherwise, the response will be cached.
api_version: 2026-01
api_name: hydrogen
source_url:
html: https://shopify.dev/docs/api/hydrogen/latest/utilities/caching/createwithcache
md: https://shopify.dev/docs/api/hydrogen/latest/utilities/caching/createwithcache.md
---
# createWithCache
Creates utility functions to store data in cache with stale-while-revalidate support.
* Use `withCache.fetch` to simply fetch data from a third-party API. Fetches data from a URL and caches the result according to the strategy provided. When the response is not successful (e.g. status code >= 400), the caching is skipped automatically and the returned `data` is `null`. You can also prevent caching by using the `shouldCacheResponse` option and returning `false` from the function you pass in. For example, you might want to fetch data from a third-party GraphQL API but not cache the result if the GraphQL response body contains errors.
* Use the more advanced `withCache.run` to execute any asynchronous operation. Utility function that executes asynchronous operations and caches the result according to the strategy provided. Use this to do any type of asynchronous operation where `withCache.fetch` is insufficient. For example, when making multiple calls to a third-party API where the result of all of them needs to be cached under the same cache key. Whatever data is returned from the `fn` will be cached according to the strategy provided.
**Note:** To prevent caching the result you must throw an error. Otherwise, the result will be cached. For example, if you call \fetch\ but the response is not successful (e.g. status code \>= 400), you should throw an error. Otherwise, the response will be cached.
## createWithCache(**[cacheOptions](#arguments-propertydetail-cacheoptions)**)
### Parameters
* **cacheOptions**
**CreateWithCacheOptions**
**required**
### Returns
* **WithCache**
### ### WithCache
* **fetch**
**\(url: string, requestInit: RequestInit, options: WithCacheFetchOptions\) => Promise<{ data: T; response: Response; }>**
* **run**
**\(options: WithCacheRunOptions\, fn: ({ addDebugData }: CacheActionFunctionParam) => T | Promise\) => Promise\**
### CreateWithCacheOptions
* cache
An instance that implements the \[Cache API]\(https://developer.mozilla.org/en-US/docs/Web/API/Cache)
```ts
Cache
```
* request
The \`request\` object is used by the Subrequest profiler, and to access certain headers for debugging
```ts
CrossRuntimeRequest
```
* waitUntil
The \`waitUntil\` function is used to keep the current request/response lifecycle alive even after a response has been sent. It should be provided by your platform.
```ts
WaitUntil
```
### CrossRuntimeRequest
* headers
```ts
{ [key: string]: any; get?: (key: string) => string; }
```
* method
```ts
string
```
* url
```ts
string
```
### WithCache
* fetch
```ts
(url: string, requestInit: RequestInit, options: WithCacheFetchOptions) => Promise<{ data: T; response: Response; }>
```
* run
```ts
(options: WithCacheRunOptions, fn: ({ addDebugData }: CacheActionFunctionParam) => T | Promise) => Promise
```
### WithCacheFetchOptions
* cacheKey
The cache key for this fetch
```ts
CacheKey
```
* cacheStrategy
Use the \`CachingStrategy\` to define a custom caching mechanism for your data. Or use one of the pre-defined caching strategies: \[\`CacheNone\`]\(/docs/api/hydrogen/utilities/cachenone), \[\`CacheShort\`]\(/docs/api/hydrogen/utilities/cacheshort), \[\`CacheLong\`]\(/docs/api/hydrogen/utilities/cachelong).
```ts
CachingStrategy
```
* displayName
```ts
string
```
* shouldCacheResponse
Useful to avoid e.g. caching a successful response that contains an error in the body
```ts
(body: T, response: Response) => boolean
```
### CacheKey
The cache key is used to uniquely identify a value in the cache.
```ts
string | readonly unknown[]
```
### CachingStrategy
Use the \`CachingStrategy\` to define a custom caching mechanism for your data. Or use one of the pre-defined caching strategies: CacheNone, CacheShort, CacheLong.
* maxAge
The maximum amount of time in seconds that a resource will be considered fresh. See \`max-age\` in the \[MDN docs]\(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#:\~:text=Response%20Directives-,max%2Dage,-The%20max%2Dage).
```ts
number
```
* mode
The caching mode, generally \`public\`, \`private\`, or \`no-store\`.
```ts
string
```
* sMaxAge
Similar to \`maxAge\` but specific to shared caches. See \`s-maxage\` in the \[MDN docs]\(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#s-maxage).
```ts
number
```
* staleIfError
Indicate that the cache should serve the stale response if an error occurs while revalidating the cache. See \`stale-if-error\` in the \[MDN docs]\(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-if-error).
```ts
number
```
* staleWhileRevalidate
Indicate that the cache should serve the stale response in the background while revalidating the cache. See \`stale-while-revalidate\` in the \[MDN docs]\(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-while-revalidate).
```ts
number
```
### WithCacheRunOptions
* cacheKey
The cache key for this run
```ts
CacheKey
```
* cacheStrategy
Use the \`CachingStrategy\` to define a custom caching mechanism for your data. Or use one of the pre-defined caching strategies: \[\`CacheNone\`]\(/docs/api/hydrogen/utilities/cachenone), \[\`CacheShort\`]\(/docs/api/hydrogen/utilities/cacheshort), \[\`CacheLong\`]\(/docs/api/hydrogen/utilities/cachelong).
```ts
CachingStrategy
```
* shouldCacheResult
Useful to avoid accidentally caching bad results
```ts
(value: T) => boolean
```
### CacheActionFunctionParam
* addDebugData
```ts
(info: AddDebugDataParam) => void
```
### AddDebugDataParam
* displayName
```ts
string
```
* response
```ts
Pick
```
Examples
### Examples
* #### Example code
##### Description
I am the default example
##### JavaScript
```js
// In your app's `server.ts` file:
import * as reactRouterBuild from 'virtual:react-router/server-build';
import {
createWithCache,
CacheLong,
createRequestHandler,
} from '@shopify/hydrogen';
export default {
async fetch(request, env, executionContext) {
const cache = await caches.open('my-cms');
const withCache = createWithCache({
cache,
waitUntil: executionContext.waitUntil.bind(executionContext),
request,
});
// 1. Create a custom utility to query a third-party API:
const fetchMyCMS = async (query) => {
const {data, response} = await withCache.fetch(
'https://my-cms.com/api',
{
method: 'POST',
body: query,
headers: {Authorization: 'Bearer 123'},
},
{
// Optionally, specify a cache strategy.
// Default is CacheShort().
cacheStrategy: CacheLong(),
// Cache if there are no data errors or a specific data that make this result not suited for caching
shouldCacheResponse: (result) =>
!(result?.errors || result?.isLoggedIn),
// Optionally, add extra information to show
// in the Subrequest Profiler utility.
displayName: 'My CMS query',
},
);
// Access the response properties:
console.log(data, response.headers);
return data;
};
// 2. Or Create a more advanced utility to query multiple APIs under the same cache key:
const fetchMultipleCMS = (options) => {
// Prefix the cache key and make it unique based on arguments.
return withCache.run(
{
// Define a cache key that is unique to this query
cacheKey: ['my-cms-composite', options.id, options.handle],
// Optionally, specify a cache strategy.
// Default is CacheShort().
cacheStrategy: CacheLong(),
// Cache if there are no data errors or a specific data that make this result not suited for caching
shouldCacheResponse: (result) =>
!(result?.errors || result?.isLoggedIn),
},
async (params) => {
// Run multiple subrequests in parallel, or any other async operations.
const [response1, response2] = await Promise.all([
fetch('https://my-cms-1.com/api', {
method: 'POST',
body: JSON.stringify({id: options.id}),
}),
fetch('https://my-cms-2.com/api', {
method: 'POST',
body: JSON.stringify({handle: options.handle}),
}),
]);
// Throw if any response is unsuccessful.
// This is important to prevent the results from being cached.
if (!response1.ok || !response2.ok) {
throw new Error('Failed to fetch data');
}
const [data1, data2] = await Promise.all([
response1.json(),
response2.json(),
]);
// Validate data and throw to avoid caching errors.
if (data1.errors || data2.errors) {
throw new Error('API errors');
}
// Optionally, add extra information to show
// in the Subrequest Profiler utility.
params.addDebugData({displayName: 'My CMS query'});
// Compose the result as needed.
return {
...data1,
...data2,
extra1: response1.headers.get('X-Extra'),
extra2: response2.headers.get('X-Extra'),
};
},
);
};
const handleRequest = createRequestHandler({
build: reactRouterBuild,
mode: process.env.NODE_ENV,
getLoadContext: () => ({
// Make sure to update env.d.ts to
// include these properties in `AppLoadContext`.
fetchMyCMS,
fetchMultipleCMS,
}),
});
return handleRequest(request);
},
};
```
##### TypeScript
```ts
// In your app's `server.ts` file:
import * as reactRouterBuild from 'virtual:react-router/server-build';
import {
createWithCache,
CacheLong,
createRequestHandler,
} from '@shopify/hydrogen';
export default {
async fetch(
request: Request,
env: Record,
executionContext: ExecutionContext,
) {
const cache = await caches.open('my-cms');
const withCache = createWithCache({
cache,
waitUntil: executionContext.waitUntil.bind(executionContext),
request,
});
type ExpectedResponse = {
content: unknown;
isLoggedIn: boolean;
errors?: string;
};
type MergedResponse = {
content: unknown;
isLoggedIn: boolean;
errors?: string;
extra1: string | null;
extra2: string | null;
};
// 1. Create a custom utility to query a third-party API:
const fetchMyCMS = async (query: string) => {
const {data, response} = await withCache.fetch(
'https://my-cms.com/api',
{
method: 'POST',
body: query,
headers: {Authorization: 'Bearer 123'},
},
{
// Optionally, specify a cache strategy.
// Default is CacheShort().
cacheStrategy: CacheLong(),
// Cache if there are no data errors or a specific data that make this result not suited for caching
shouldCacheResponse: (result) =>
!(result?.errors || result?.isLoggedIn),
// Optionally, add extra information to show
// in the Subrequest Profiler utility.
displayName: 'My CMS query',
},
);
// Access the response properties:
console.log(data, response.headers);
return data;
};
// 2. Or Create a more advanced utility to query multiple APIs under the same cache key:
const fetchMultipleCMS = (options: {id: string; handle: string}) => {
// Prefix the cache key and make it unique based on arguments.
return withCache.run(
{
// Define a cache key that is unique to this query
cacheKey: ['my-cms-composite', options.id, options.handle],
// Optionally, specify a cache strategy.
// Default is CacheShort().
cacheStrategy: CacheLong(),
// Cache if there are no data errors or a specific data that make this result not suited for caching
shouldCacheResult: (result: MergedResponse) =>
!(result?.errors || result?.isLoggedIn),
},
async (params) => {
// Run multiple subrequests in parallel, or any other async operations.
const [response1, response2] = await Promise.all([
fetch('https://my-cms-1.com/api', {
method: 'POST',
body: JSON.stringify({id: options.id}),
}),
fetch('https://my-cms-2.com/api', {
method: 'POST',
body: JSON.stringify({handle: options.handle}),
}),
]);
// Throw if any response is unsuccessful.
// This is important to prevent the results from being cached.
if (!response1.ok || !response2.ok) {
throw new Error('Failed to fetch data');
}
const [data1, data2] = (await Promise.all([
response1.json(),
response2.json(),
])) as [ExpectedResponse, ExpectedResponse];
// Validate data and throw to avoid caching errors.
if (data1.errors || data2.errors) {
throw new Error('API errors');
}
// Optionally, add extra information to show
// in the Subrequest Profiler utility.
params.addDebugData({displayName: 'My CMS query'});
// Compose the result as needed.
return {
...data1,
...data2,
extra1: response1.headers.get('X-Extra'),
extra2: response2.headers.get('X-Extra'),
} as MergedResponse;
},
);
};
const handleRequest = createRequestHandler({
build: reactRouterBuild,
mode: process.env.NODE_ENV,
getLoadContext: () => ({
// Make sure to update env.d.ts to
// include these properties in `AppLoadContext`.
fetchMyCMS,
fetchMultipleCMS,
}),
});
return handleRequest(request);
},
};
```