---
title: Cache using fetch
description: Determine how to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch request.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Caching ](https://developers.cloudflare.com/search/?tags=Caching)[ Middleware ](https://developers.cloudflare.com/search/?tags=Middleware)[ JavaScript ](https://developers.cloudflare.com/search/?tags=JavaScript)[ TypeScript ](https://developers.cloudflare.com/search/?tags=TypeScript)[ Python ](https://developers.cloudflare.com/search/?tags=Python)[ Rust ](https://developers.cloudflare.com/search/?tags=Rust) 

# Cache using fetch

**Last reviewed:**  over 5 years ago 

Determine how to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch request.

If you want to get started quickly, click on the button below.

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/docs-examples/tree/main/workers/cache-using-fetch)

This creates a repository in your GitHub account and deploys the application to Cloudflare Workers.

* [  JavaScript ](#tab-panel-9246)
* [  TypeScript ](#tab-panel-9247)
* [  Hono ](#tab-panel-9248)
* [  Python ](#tab-panel-9249)
* [  Rust ](#tab-panel-9250)

JavaScript

```

export default {

  async fetch(request) {

    const url = new URL(request.url);

    // Only use the path for the cache key, removing query strings

    // and always store using HTTPS, for example, https://www.example.com/file-uri-here

    const someCustomKey = `https://${url.hostname}${url.pathname}`;

    let response = await fetch(request, {

      cf: {

        // Always cache this fetch regardless of content type

        // for a max of 5 seconds before revalidating the resource

        cacheTtl: 5,

        cacheEverything: true,

        //Enterprise only feature, see Cache API for other plans

        cacheKey: someCustomKey,

      },

    });

    // Reconstruct the Response object to make its headers mutable.

    response = new Response(response.body, response);

    // Set cache control headers to cache on browser for 25 minutes

    response.headers.set("Cache-Control", "max-age=1500");

    return response;

  },

};


```

TypeScript

```

export default {

  async fetch(request): Promise<Response> {

    const url = new URL(request.url);

    // Only use the path for the cache key, removing query strings

    // and always store using HTTPS, for example, https://www.example.com/file-uri-here

    const someCustomKey = `https://${url.hostname}${url.pathname}`;

    let response = await fetch(request, {

      cf: {

        // Always cache this fetch regardless of content type

        // for a max of 5 seconds before revalidating the resource

        cacheTtl: 5,

        cacheEverything: true,

        //Enterprise only feature, see Cache API for other plans

        cacheKey: someCustomKey,

      },

    });

    // Reconstruct the Response object to make its headers mutable.

    response = new Response(response.body, response);

    // Set cache control headers to cache on browser for 25 minutes

    response.headers.set("Cache-Control", "max-age=1500");

    return response;

  },

} satisfies ExportedHandler;


```

TypeScript

```

import { Hono } from 'hono';


type Bindings = {};


const app = new Hono<{ Bindings: Bindings }>();


app.all('*', async (c) => {

  const url = new URL(c.req.url);


  // Only use the path for the cache key, removing query strings

  // and always store using HTTPS, for example, https://www.example.com/file-uri-here

  const someCustomKey = `https://${url.hostname}${url.pathname}`;


  // Fetch the request with custom cache settings

  let response = await fetch(c.req.raw, {

    cf: {

      // Always cache this fetch regardless of content type

      // for a max of 5 seconds before revalidating the resource

      cacheTtl: 5,

      cacheEverything: true,

      // Enterprise only feature, see Cache API for other plans

      cacheKey: someCustomKey,

    },

  });


  // Reconstruct the Response object to make its headers mutable

  response = new Response(response.body, response);


  // Set cache control headers to cache on browser for 25 minutes

  response.headers.set("Cache-Control", "max-age=1500");


  return response;

});


export default app;


```

Python

```

from workers import WorkerEntrypoint

from pyodide.ffi import to_js as _to_js

from js import Response, URL, Object, fetch


def to_js(x):

    return _to_js(x, dict_converter=Object.fromEntries)


class Default(WorkerEntrypoint):

    async def fetch(self, request):

        url = URL.new(request.url)


        # Only use the path for the cache key, removing query strings

        # and always store using HTTPS, for example, https://www.example.com/file-uri-here

        some_custom_key = f"https://{url.hostname}{url.pathname}"


        response = await fetch(

            request,

            cf=to_js({

                # Always cache this fetch regardless of content type

                # for a max of 5 seconds before revalidating the resource

                "cacheTtl": 5,

                "cacheEverything": True,

                # Enterprise only feature, see Cache API for other plans

                "cacheKey": some_custom_key,

            }),

        )


        # Reconstruct the Response object to make its headers mutable

        response = Response.new(response.body, response)


        # Set cache control headers to cache on browser for 25 minutes

        response.headers["Cache-Control"] = "max-age=1500"


        return response


```

```

use worker::*;


#[event(fetch)]

async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {

    let url = req.url()?;


    // Only use the path for the cache key, removing query strings

    // and always store using HTTPS, for example, https://www.example.com/file-uri-here

    let custom_key = format!(

        "https://{host}{path}",

        host = url.host_str().unwrap(),

        path = url.path()

    );


    let request = Request::new_with_init(

        url.as_str(),

        &RequestInit {

            headers: req.headers().clone(),

            method: req.method(),

            cf: CfProperties {

                // Always cache this fetch regardless of content type

                // for a max of 5 seconds before revalidating the resource

                cache_ttl: Some(5),

                cache_everything: Some(true),

                // Enterprise only feature, see Cache API for other plans

                cache_key: Some(custom_key),

                ..CfProperties::default()

            },

            ..RequestInit::default()

        },

    )?;


    let mut response = Fetch::Request(request).send().await?;


    // Set cache control headers to cache on browser for 25 minutes

    let _ = response.headers_mut().set("Cache-Control", "max-age=1500");

    Ok(response)

}


```

## Caching HTML resources

JavaScript

```

// Force Cloudflare to cache an asset

fetch(event.request, { cf: { cacheEverything: true } });


```

Setting the cache level to **Cache Everything** will override the default cacheability of the asset. For time-to-live (TTL), Cloudflare will still rely on headers set by the origin.

## Custom cache keys

Note

This feature is available only to Enterprise customers.

A request's cache key is what determines if two requests are the same for caching purposes. If a request has the same cache key as some previous request, then Cloudflare can serve the same cached response for both. For more about cache keys, refer to the [Create custom cache keys](https://developers.cloudflare.com/cache/how-to/cache-keys/#create-custom-cache-keys) documentation.

JavaScript

```

// Set cache key for this request to "some-string".

fetch(event.request, { cf: { cacheKey: "some-string" } });


```

Normally, Cloudflare computes the cache key for a request based on the request's URL. Sometimes, though, you may like different URLs to be treated as if they were the same for caching purposes. For example, if your website content is hosted from both Amazon S3 and Google Cloud Storage - you have the same content in both places, and you can use a Worker to randomly balance between the two. However, you do not want to end up caching two copies of your content. You could utilize custom cache keys to cache based on the original request URL rather than the subrequest URL:

* [  JavaScript ](#tab-panel-9251)
* [  TypeScript ](#tab-panel-9252)
* [  Hono ](#tab-panel-9253)

JavaScript

```

export default {

  async fetch(request) {

    let url = new URL(request.url);


    if (Math.random() < 0.5) {

      url.hostname = "example.s3.amazonaws.com";

    } else {

      url.hostname = "example.storage.googleapis.com";

    }


    let newRequest = new Request(url, request);

    return fetch(newRequest, {

      cf: { cacheKey: request.url },

    });

  },

};


```

TypeScript

```

export default {

  async fetch(request): Promise<Response> {

    let url = new URL(request.url);


    if (Math.random() < 0.5) {

      url.hostname = "example.s3.amazonaws.com";

    } else {

      url.hostname = "example.storage.googleapis.com";

    }


    let newRequest = new Request(url, request);

    return fetch(newRequest, {

      cf: { cacheKey: request.url },

    });

  },

} satisfies ExportedHandler;


```

TypeScript

```

import { Hono } from 'hono';


type Bindings = {};


const app = new Hono<{ Bindings: Bindings }>();


app.all('*', async (c) => {

  const originalUrl = c.req.url;

  const url = new URL(originalUrl);


  // Randomly select a storage backend

  if (Math.random() < 0.5) {

    url.hostname = "example.s3.amazonaws.com";

  } else {

    url.hostname = "example.storage.googleapis.com";

  }


  // Create a new request to the selected backend

  const newRequest = new Request(url, c.req.raw);


  // Fetch using the original URL as the cache key

  return fetch(newRequest, {

    cf: { cacheKey: originalUrl },

  });

});


export default app;


```

Workers operating on behalf of different zones cannot affect each other's cache. You can only override cache keys when making requests within your own zone (in the above example `event.request.url` was the key stored), or requests to hosts that are not on Cloudflare. When making a request to another Cloudflare zone (for example, belonging to a different Cloudflare customer), that zone fully controls how its own content is cached within Cloudflare; you cannot override it.

## Override based on origin response code

JavaScript

```

// Force response to be cached for 86400 seconds for 200 status

// codes, 1 second for 404, and do not cache 500 errors.

fetch(request, {

  cf: { cacheTtlByStatus: { "200-299": 86400, 404: 1, "500-599": 0 } },

});


```

This option is a version of the `cacheTtl` feature which chooses a TTL based on the response's status code and does not automatically set `cacheEverything: true`. If the response to this request has a status code that matches, Cloudflare will cache for the instructed time, and override cache directives sent by the origin. You can review [details on the cacheTtl feature on the Request page](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties).

## Customize cache behavior based on request file type

Using custom cache keys and overrides based on response code, you can write a Worker that sets the TTL based on the response status code from origin, and request file type.

The following example demonstrates how you might use this to cache requests for streaming media assets:

* [  Module Worker ](#tab-panel-9254)
* [  Service Worker ](#tab-panel-9255)

index.js

```

export default {

  async fetch(request) {

    // Instantiate new URL to make it mutable

    const newRequest = new URL(request.url);


    const customCacheKey = `${newRequest.hostname}${newRequest.pathname}`;

    const queryCacheKey = `${newRequest.hostname}${newRequest.pathname}${newRequest.search}`;


    // Different asset types usually have different caching strategies. Most of the time media content such as audio, videos and images that are not user-generated content would not need to be updated often so a long TTL would be best. However, with HLS streaming, manifest files usually are set with short TTLs so that playback will not be affected, as this files contain the data that the player would need. By setting each caching strategy for categories of asset types in an object within an array, you can solve complex needs when it comes to media content for your application


    const cacheAssets = [

      {

        asset: "video",

        key: customCacheKey,

        regex:

          /(.*\/Video)|(.*\.(m4s|mp4|ts|avi|mpeg|mpg|mkv|bin|webm|vob|flv|m2ts|mts|3gp|m4v|wmv|qt))/,

        info: 0,

        ok: 31556952,

        redirects: 30,

        clientError: 10,

        serverError: 0,

      },

      {

        asset: "image",

        key: queryCacheKey,

        regex:

          /(.*\/Images)|(.*\.(jpg|jpeg|png|bmp|pict|tif|tiff|webp|gif|heif|exif|bat|bpg|ppm|pgn|pbm|pnm))/,

        info: 0,

        ok: 3600,

        redirects: 30,

        clientError: 10,

        serverError: 0,

      },

      {

        asset: "frontEnd",

        key: queryCacheKey,

        regex: /^.*\.(css|js)/,

        info: 0,

        ok: 3600,

        redirects: 30,

        clientError: 10,

        serverError: 0,

      },

      {

        asset: "audio",

        key: customCacheKey,

        regex:

          /(.*\/Audio)|(.*\.(flac|aac|mp3|alac|aiff|wav|ogg|aiff|opus|ape|wma|3gp))/,

        info: 0,

        ok: 31556952,

        redirects: 30,

        clientError: 10,

        serverError: 0,

      },

      {

        asset: "directPlay",

        key: customCacheKey,

        regex: /.*(\/Download)/,

        info: 0,

        ok: 31556952,

        redirects: 30,

        clientError: 10,

        serverError: 0,

      },

      {

        asset: "manifest",

        key: customCacheKey,

        regex: /^.*\.(m3u8|mpd)/,

        info: 0,

        ok: 3,

        redirects: 2,

        clientError: 1,

        serverError: 0,

      },

    ];


    const { asset, regex, ...cache } =

      cacheAssets.find(({ regex }) => newRequest.pathname.match(regex)) ?? {};


    const newResponse = await fetch(request, {

      cf: {

        cacheKey: cache.key,

        polish: false,

        cacheEverything: true,

        cacheTtlByStatus: {

          "100-199": cache.info,

          "200-299": cache.ok,

          "300-399": cache.redirects,

          "400-499": cache.clientError,

          "500-599": cache.serverError,

        },

        cacheTags: ["static"],

      },

    });


    const response = new Response(newResponse.body, newResponse);


    // For debugging purposes

    response.headers.set("debug", JSON.stringify(cache));

    return response;

  },

};


```

Service Workers are deprecated

Service Workers are deprecated, but still supported. We recommend using [Module Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) instead. New features may not be supported for Service Workers.

index.js

```

addEventListener("fetch", (event) => {

  return event.respondWith(handleRequest(event.request));

});


async function handleRequest(request) {

  // Instantiate new URL to make it mutable

  const newRequest = new URL(request.url);


  // Set `const` to be used in the array later on

  const customCacheKey = `${newRequest.hostname}${newRequest.pathname}`;

  const queryCacheKey = `${newRequest.hostname}${newRequest.pathname}${newRequest.search}`;


  // Set all variables needed to manipulate Cloudflare's cache using the fetch API in the `cf` object. You will be passing these variables in the objects down below.

  const cacheAssets = [

    {

      asset: "video",

      key: customCacheKey,

      regex:

        /(.*\/Video)|(.*\.(m4s|mp4|ts|avi|mpeg|mpg|mkv|bin|webm|vob|flv|m2ts|mts|3gp|m4v|wmv|qt))/,

      info: 0,

      ok: 31556952,

      redirects: 30,

      clientError: 10,

      serverError: 0,

    },

    {

      asset: "image",

      key: queryCacheKey,

      regex:

        /(.*\/Images)|(.*\.(jpg|jpeg|png|bmp|pict|tif|tiff|webp|gif|heif|exif|bat|bpg|ppm|pgn|pbm|pnm))/,

      info: 0,

      ok: 3600,

      redirects: 30,

      clientError: 10,

      serverError: 0,

    },

    {

      asset: "frontEnd",

      key: queryCacheKey,

      regex: /^.*\.(css|js)/,

      info: 0,

      ok: 3600,

      redirects: 30,

      clientError: 10,

      serverError: 0,

    },

    {

      asset: "audio",

      key: customCacheKey,

      regex:

        /(.*\/Audio)|(.*\.(flac|aac|mp3|alac|aiff|wav|ogg|aiff|opus|ape|wma|3gp))/,

      info: 0,

      ok: 31556952,

      redirects: 30,

      clientError: 10,

      serverError: 0,

    },

    {

      asset: "directPlay",

      key: customCacheKey,

      regex: /.*(\/Download)/,

      info: 0,

      ok: 31556952,

      redirects: 30,

      clientError: 10,

      serverError: 0,

    },

    {

      asset: "manifest",

      key: customCacheKey,

      regex: /^.*\.(m3u8|mpd)/,

      info: 0,

      ok: 3,

      redirects: 2,

      clientError: 1,

      serverError: 0,

    },

  ];


  // the `.find` method is used to find elements in an array (`cacheAssets`), in this case, `regex`, which can passed to the .`match` method to match on file extensions to cache, since they are many media types in the array. If you want to add more types, update the array. Refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find for more information.

  const { asset, regex, ...cache } =

    cacheAssets.find(({ regex }) => newRequest.pathname.match(regex)) ?? {};


  const newResponse = await fetch(request, {

    cf: {

      cacheKey: cache.key,

      polish: false,

      cacheEverything: true,

      cacheTtlByStatus: {

        "100-199": cache.info,

        "200-299": cache.ok,

        "300-399": cache.redirects,

        "400-499": cache.clientError,

        "500-599": cache.serverError,

      },

      cacheTags: ["static"],

    },

  });


  const response = new Response(newResponse.body, newResponse);


  // For debugging purposes

  response.headers.set("debug", JSON.stringify(cache));

  return response;

}


```

## Using the HTTP Cache API

The `cache` mode can be set in `fetch` options. Currently Workers only support the `no-store` and `no-cache` mode for controlling the cache. When `no-store` is supplied the cache is bypassed on the way to the origin and the request is not cacheable. When `no-cache` is supplied the cache is forced to revalidate the currently cached response with the origin.

JavaScript

```

fetch(request, { cache: 'no-store'});

fetch(request, { cache: 'no-cache'});


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/workers/","name":"Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/workers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/examples/cache-using-fetch/","name":"Cache using fetch"}}]}
```
