Skip to content

The Cache API

In one line: The Cache API (caches global) is a persistent, origin-scoped key/value store for Request/Response pairs. Service workers use it to store resources during install and serve them offline; unlike the browser HTTP cache, the Cache API is fully programmatic — nothing is stored or evicted without your code doing it.

caches is a CacheStorage instance available in service workers and in pages (since Chrome 43 / Firefox 41). It manages a named collection of Cache objects.

// Available in both service workers and pages
const cache = await caches.open('my-cache-v1');
const cache = await caches.open('app-shell-v3');

caches.open() creates the cache if it does not exist. Cache names are arbitrary strings; use versioned names (app-shell-v3) so you can delete old caches in activate.

cache.add(request) — fetch and store one resource

Section titled “cache.add(request) — fetch and store one resource”
await cache.add('/offline.html'); // fetches, then stores

cache.addAll(requests) — fetch and store multiple resources atomically

Section titled “cache.addAll(requests) — fetch and store multiple resources atomically”
await cache.addAll([
'/',
'/app.js',
'/app.css',
'/offline.html',
]);

addAll is atomic: if any request fails, nothing is stored. Use it in install inside event.waitUntil() so a failed pre-cache aborts the install.

cache.put(request, response) — store a response you already have

Section titled “cache.put(request, response) — store a response you already have”
const response = await fetch(event.request);
await cache.put(event.request, response.clone());

put does not fetch — it stores the Response you pass directly. You must clone() the response before storing if you also intend to use it (responses are single-read streams).

cache.match(request, options?) — look up one entry

Section titled “cache.match(request, options?) — look up one entry”
const cached = await cache.match('/app.js');
if (cached) return cached;

caches.match(request) — query all caches in insertion order

Section titled “caches.match(request) — query all caches in insertion order”
const cached = await caches.match(event.request);

caches.match() searches every open cache and returns the first match. Useful when you don’t know which cache holds a resource.

await cache.match(request, {
ignoreSearch: true, // ignore query string (?v=1)
ignoreMethod: true, // match POST as if GET
ignoreVary: true, // ignore Vary header
});
// Remove one entry from a cache
await cache.delete('/old-resource.js');
// Delete an entire named cache
await caches.delete('app-shell-v2');
// List all cache names
const names = await caches.keys();

The standard pattern is to delete old caches in the activate event after a new version installs:

self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names
.filter((n) => n !== 'app-shell-v3') // keep only the current cache
.map((n) => caches.delete(n))
)
)
);
});

Cache Storage is subject to the origin’s storage quota (shared with IndexedDB, OPFS, and other storage APIs). Browsers evict data when the device is under storage pressure, starting with origins that have not been used recently — unless the origin has requested persistent storage. See Persistence, quotas, and eviction for details.

A cross-origin response fetched with no-cors is opaque (status: 0, body unreadable). You can cache an opaque response, but:

  • You cannot inspect its status code to verify it succeeded.
  • It may be counted as a large entry (browsers pad opaque responses in quota accounting to prevent timing attacks).
  • Stale opaque responses are indistinguishable from failed ones.

Prefer same-origin or CORS-enabled resources in caches. If you must cache an opaque response, always pair it with a revalidation strategy.

  • Version your cache names (shell-v3, not shell) and delete previous versions in activate.
  • Use cache.addAll() in install to pre-cache the app shell atomically.
  • Always clone() a response before calling cache.put() if you also return it to the caller.
  • Prefer cache.match() over caches.match() when you know which cache to search — it’s faster.
  • Monitor storage usage in DevTools → Application → Storage to catch quota creep.