---
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: 2025-05
api_name: hydrogen
source_url:
  html: >-
    https://shopify.dev/docs/api/hydrogen/2025-05/utilities/caching/createwithcache
  md: >-
    https://shopify.dev/docs/api/hydrogen/2025-05/utilities/caching/createwithcache.md
---

# create​With​Cache

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 \<code>fetch\</code> but the response is not successful (e.g. status code \&gt;= 400), you should throw an error. Otherwise, the response will be cached.

## create​With​Cache(**[cacheOptions](#arguments-propertydetail-cacheoptions)**​)

### Parameters

* **cacheOptions**

  **CreateWithCacheOptions**

  **required**

### Returns

* **WithCache**

  ### ### WithCache

  * **fetch**

    **\<T>(url: string, requestInit: RequestInit, options: WithCacheFetchOptions\<T>) => Promise<{ data: T; response: Response; }>**

  * **run**

    **\<T>(options: WithCacheRunOptions\<T>, fn: ({ addDebugData }: CacheActionFunctionParam) => T | Promise\<T>) => Promise\<T>**

### 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
  <T>(url: string, requestInit: RequestInit, options: WithCacheFetchOptions<T>) => Promise<{ data: T; response: Response; }>
  ```

* run

  ```ts
  <T>(options: WithCacheRunOptions<T>, fn: ({ addDebugData }: CacheActionFunctionParam) => T | Promise<T>) => Promise<T>
  ```

### 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<Response, 'url' | 'status' | 'statusText' | 'headers'>
  ```

Examples

### Examples

* #### Example code

  ##### Description

  I am the default example

  ##### JavaScript

  ```js
  // In your app's `server.ts` file:
  // @ts-expect-error
  import * as reactRouterBuild from 'virtual:react-router/server-build';
  import {createWithCache, CacheLong} from '@shopify/hydrogen';
  // Use another `createRequestHandler` if deploying off oxygen
  import {createRequestHandler} from '@shopify/remix-oxygen';

  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:
  // @ts-ignore
  import * as reactRouterBuild from 'virtual:virtual:react-router/server-build';
  import {createWithCache, CacheLong} from '@shopify/hydrogen';
  // Use another `createRequestHandler` if deploying off oxygen
  import {createRequestHandler} from '@shopify/remix-oxygen';

  export default {
    async fetch(
      request: Request,
      env: Record<string, string>,
      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<ExpectedResponse>(
          '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);
    },
  };
  ```
