# CacheStorage and the caches global

> The caches global: opening, listing, and deleting Cache objects, opaque-response padding against quota, and the TypeError and QuotaExceededError write failures.

`CacheStorage`, exposed as the `caches` global in windows, workers, and service workers, is the
origin's registry of named `Cache` objects, each a list of `Request`/`Response` pairs that a
service worker can answer `fetch` events from while offline. It is independent of the HTTP cache:
nothing enters or leaves it except through script, and HTTP freshness headers are ignored.

## Syntax

```js
caches.open(cacheName)
caches.has(cacheName)
caches.delete(cacheName)
caches.keys()
caches.match(request)
caches.match(request, options)
```

Every method returns a promise. `open()` creates the named cache when it does not exist, so a
version bump in the name (`shell-v4`) is enough to start a fresh store. `match()` on
`CacheStorage` searches every cache in creation order and resolves with the first hit, or
`undefined`. The interface is available from the main thread in Chrome 43, Firefox 41, and
Safari 11.1 (BCD `api.CacheStorage`), and in service workers from Chrome 40.

## Parameters

| Method | Parameter | Type | Meaning |
|---|---|---|---|
| `open`, `has`, `delete` | `cacheName` | string | The cache's name. Names are case-sensitive and scoped to the origin; two service workers on one origin share the same namespace. |
| `match` | `request` | `Request` or string | The request to look up. A string is converted to a `Request` for the current base URL. |
| `match` | `options.ignoreSearch` | boolean | Ignore the query string when comparing URLs. Default `false`. |
| `match` | `options.ignoreMethod` | boolean | Match non-`GET` requests. Default `false`, so a `POST` does not match. |
| `match` | `options.ignoreVary` | boolean | Ignore the stored response's `Vary` header. Default `false`. |
| `match` | `options.cacheName` | string | Restrict the search to one cache. Chromium supports only `ignoreSearch` and `cacheName` on `CacheStorage.match()` (BCD `api.CacheStorage.match`). |

The write methods live on `Cache`, not on `CacheStorage`: `cache.put(request, response)` stores a
pair you already hold, `cache.add(request)` and `cache.addAll(requests)` fetch and store, and
`cache.delete(request)` removes one entry.

## Exceptions

| Exception | Where | When |
|---|---|---|
| `TypeError` | `cache.put()` | The request URL scheme is not `http:` or `https:`, the response status is `206 Partial Content`, or the response carries `Vary: *`. The promise rejects. |
| `TypeError` | `cache.add()`, `cache.addAll()` | The fetched response is not `ok` (status outside 200 to 299), which includes every opaque response because its status is `0`. Use `fetch()` plus `put()` to store an opaque response deliberately. |
| `QuotaExceededError` `DOMException` | any write | The origin's quota is exhausted. The promise rejects; previously stored entries are untouched. |
| `SecurityError` `DOMException` | any `caches` method | The origin is opaque (a sandboxed `<iframe>` without `allow-same-origin`) or storage is disabled for the site. |

Cache Storage does not expire entries. A response stored with `Cache-Control: max-age=60` is
returned a year later exactly as stored; freshness is the service worker's job.

## Browser support

`CacheStorage` and `Cache` have shipped in every engine since Chrome 43, Firefox 41, and
Safari 11.1 (BCD `api.CacheStorage`, `api.Cache`), in windows, dedicated workers, and service
workers alike, and only on secure origins. `cache.add()` requires HTTPS from Chrome 46. The one
place the API is missing is a WebView or browser with site storage disabled, where every method
rejects with `SecurityError`.

## Quota accounting

Cache Storage shares the origin's single quota with IndexedDB and OPFS, as reported by
[`navigator.storage.estimate()`](/reference/storage/quota-estimate/). Opaque responses are
charged with padding so that a page cannot measure a cross-origin resource's size through the
quota API: in Chromium each opaque response costs roughly 7 MB of reported usage regardless of
its real size ([Understanding storage quota](https://developer.chrome.com/docs/workbox/understanding-storage-quota),
developer.chrome.com). Request cross-origin assets in `cors` mode, or set `crossorigin` on the
tag that loads them, so the response is not opaque and is charged at its real size.

## Examples

The three examples cover the install-time precache, a runtime cache fed from `fetch`, and the
feature check a page needs before touching `caches`.

### Versioned precache with cleanup on activate

The cache name carries the version. `install` fills the new cache; `activate` deletes every
cache whose name differs, so the previous deploy's files are reclaimed instead of accumulating.

```js
const CACHE = 'shell-v4';
const ASSETS = ['/', '/app.css', '/app.js', '/offline.html'];

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(ASSETS)));
});

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

`addAll()` is atomic: if one of the four requests returns a 404 the whole call rejects with
`TypeError` and the `install` fails, which is the behaviour you want for an app shell. Any
`fetch` handler that calls `caches.match(event.request)` keeps answering from the old cache until
the new worker activates.

### Storing a response and returning it in one handler

A `Response` body can be read once. Clone it before `put()` when the same response also goes to
the page, and do not await the `put()` inside `respondWith()`, which would delay the page for a
disk write.

```js
self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return;
  event.respondWith(
    caches.match(event.request).then((hit) => {
      if (hit) return hit;
      return fetch(event.request).then((response) => {
        if (response.ok) {
          const copy = response.clone();
          event.waitUntil(caches.open('runtime-v1').then((cache) => cache.put(event.request, copy)));
        }
        return response;
      });
    })
  );
});
```

The `response.ok` guard keeps 404s and opaque responses out of the cache; without it, an
offline user would be served yesterday's error page as a hit.

### Detecting the API and falling back to the network

`caches` is absent on insecure origins and in browsers older than those in the Syntax section.
Code that runs in the page, outside the service worker, should check before touching it.

```js
async function cachedOrNetwork(url) {
  if (!('caches' in self)) {
    return fetch(url); // no Cache Storage: plain network, no offline copy
  }
  const hit = await caches.match(url);
  return hit ?? fetch(url);
}
```

The same function works in a worker and in a window because both expose `self`.

:::observed
Chrome DevTools, Application > Storage > Cache storage, lists each named cache under the origin
and, when you select one, a table of its entries with **Name**, **Response-Type**, **Content-Type**,
**Content-Length**, **Time Cached**, and **Vary Header** columns; an opaque entry shows
`opaque` under **Response-Type** with `0` under **Content-Length** while the Storage usage bar
grows by megabytes, which is the padding described above ([Debug Progressive Web
Apps](https://developer.chrome.com/docs/devtools/progressive-web-apps), developer.chrome.com).
:::

## See also

- [Service Workers: CacheStorage interface](https://w3c.github.io/ServiceWorker/#cachestorage-interface) (w3.org)
- [Understanding storage quota](https://developer.chrome.com/docs/workbox/understanding-storage-quota) (developer.chrome.com)
- [The Cache API](/reference/service-worker/cache-api/)
- [Caching strategies](/reference/service-worker/caching-strategies/)
- [StorageManager.estimate()](/reference/storage/quota-estimate/)
- [Eviction and best-effort storage](/reference/storage/eviction/)