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.
The caches global
Section titled “The caches global”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 pagesconst cache = await caches.open('my-cache-v1');Opening a cache
Section titled “Opening a cache”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.
Adding resources
Section titled “Adding resources”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 storescache.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).
Querying
Section titled “Querying”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.
Match options
Section titled “Match options”await cache.match(request, { ignoreSearch: true, // ignore query string (?v=1) ignoreMethod: true, // match POST as if GET ignoreVary: true, // ignore Vary header});Deleting entries and caches
Section titled “Deleting entries and caches”// Remove one entry from a cacheawait cache.delete('/old-resource.js');
// Delete an entire named cacheawait caches.delete('app-shell-v2');
// List all cache namesconst 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)) ) ) );});Storage limits
Section titled “Storage limits”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.
Caching opaque responses
Section titled “Caching opaque responses”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.
Practical checklist
Section titled “Practical checklist”- Version your cache names (
shell-v3, notshell) and delete previous versions inactivate. - Use
cache.addAll()ininstallto pre-cache the app shell atomically. - Always
clone()a response before callingcache.put()if you also return it to the caller. - Prefer
cache.match()overcaches.match()when you know which cache to search — it’s faster. - Monitor storage usage in DevTools → Application → Storage to catch quota creep.
Cross-references
Section titled “Cross-references”- Caching strategies — the patterns that use the Cache API
- The Fetch event and routing — where
caches.match()is called at request time - Persistence, quotas, and eviction — storage quotas and eviction policy