# The Cache API Guide

> Learn how the browser Cache API stores Request and Response pairs, how to match and update entries, and which expiration and quota limits matter.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-02-20 | Updated: 2026-07-18 | Topics: [Web Platform](https://flaviocopes.com/tags/platform/) | Canonical: https://flaviocopes.com/cache-api/

The Cache API stores `Request` and `Response` pairs under your site's origin.

It is commonly used by service workers to make pages work offline. You can also use it from a window or another worker.

The API is available in secure contexts, which means HTTPS in production. Local development origins such as `http://localhost` are treated as trustworthy.

## The Cache API is not the HTTP cache

Cache API entries are separate from the browser's normal HTTP cache.

The API does not automatically follow HTTP cache expiration headers. An entry remains until your code removes or replaces it, or the browser evicts site data.

Use the Cache API for URL-addressable resources such as pages, scripts, images, and API responses. Use [IndexedDB](https://flaviocopes.com/indexeddb/) for application records that are not naturally represented by requests and responses.

## Open a named cache

The global `caches` object is a `CacheStorage`.

Open a cache with `caches.open()`:

```js
const cache = await caches.open('assets-v1')
```

If the named cache does not exist, the browser creates it.

Give caches versioned names. When the contents change in an incompatible way, open a new version and remove the old one.

## Add resources

`cache.add()` fetches one resource and stores the response:

```js
const cache = await caches.open('assets-v1')
await cache.add('/styles.css')
```

`add()` rejects if the fetch fails or returns a response outside the successful 200–299 range.

Add several resources with `addAll()`:

```js
await cache.addAll([
  '/',
  '/styles.css',
  '/app.js',
])
```

If one request fails, `addAll()` rejects. Handle the error so a failed install does not look successful.

## Store a fetched response with put

Use `cache.put()` when you need to inspect or transform the response first:

```js
const request = new Request('/api/posts')
const response = await fetch(request)

if (response.ok) {
  const cache = await caches.open('api-v1')
  await cache.put(request, response)
}
```

Unlike `add()`, `put()` can store error responses if you pass one. Check the response deliberately.

Request and response bodies are streams. If you need to return a response and cache it, clone it first:

```js
const response = await fetch(request)

if (response.ok) {
  await cache.put(request, response.clone())
}

return response
```

The Cache API only stores `GET` requests. It is not a general store for POST request results.

## Match a cached response

Use `cache.match()`:

```js
const cache = await caches.open('api-v1')
const response = await cache.match('/api/posts')

if (response) {
  const posts = await response.json()
  console.log(posts)
}
```

It resolves to the first matching `Response`, or `undefined` when no entry matches.

Matching normally considers the request URL, method, and relevant `Vary` response headers. Options such as `ignoreSearch` and `ignoreVary` can loosen matching, but use them carefully. They can return a response created for different input.

Search every named cache with `caches.match()`:

```js
const response = await caches.match('/styles.css')
```

This returns the first match in cache creation order. Opening the intended named cache is clearer when cache order should not decide the result.

## Build a cache-first service worker

A service worker can answer a request from the cache and fall back to the network:

```js
self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return

  event.respondWith(
    caches.match(event.request).then(async (cachedResponse) => {
      if (cachedResponse) {
        return cachedResponse
      }

      const response = await fetch(event.request)

      if (response.ok) {
        const cache = await caches.open('runtime-v1')
        await cache.put(event.request, response.clone())
      }

      return response
    }),
  )
})
```

This is a basic cache-first strategy. It can serve stale data forever unless you add an update and deletion policy.

Different resources need different strategies. Static versioned assets suit cache-first. Frequently changing API data may need network-first or stale-while-revalidate behavior.

## CORS and opaque responses

The Cache API does not bypass CORS.

A `no-cors` fetch can produce an **opaque response**. Your code cannot inspect its status, headers, or body. `cache.put()` can store it, but `cache.add()` rejects it because an opaque response does not report a successful status.

Cache opaque responses only when you trust the URL and understand the storage and failure tradeoffs.

## List and remove entries

List the requests stored in one cache:

```js
const cache = await caches.open('assets-v1')
const requests = await cache.keys()

for (const request of requests) {
  console.log(request.url)
}
```

Delete one matching entry:

```js
await cache.delete('/old-styles.css')
```

List every named cache:

```js
const names = await caches.keys()
```

Delete a complete named cache:

```js
await caches.delete('assets-v1')
```

## Delete old cache versions

Service workers commonly remove old versions during activation:

```js
const currentCaches = new Set([
  'assets-v2',
  'runtime-v1',
])

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((names) => {
      return Promise.all(
        names
          .filter((name) => !currentCaches.has(name))
          .map((name) => caches.delete(name)),
      )
    }),
  )
})
```

Only delete cache names owned by this application. Other code on the same origin shares the same `CacheStorage`.

## Storage can be evicted

Cache storage uses the site's browser storage quota. Limits and eviction rules vary by browser, device, free disk space, and browsing mode.

Check estimated usage with the Storage API:

```js
const estimate = await navigator.storage.estimate()

console.log(estimate.usage)
console.log(estimate.quota)
```

Do not treat a cache as the only copy of important user data. The browser can evict it, and users can clear it.

See the MDN references for [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache), [`CacheStorage`](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage), and [service worker caching](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#custom_responses_to_requests).
