The Cache Storage API
Opening a cache
Section titled “Opening a cache”The Cache Storage API is exposed as the caches global inside a service worker context. To open (or create) a named cache, call caches.open('cache-v1'), which returns a Promise<Cache>. Caches are identified purely by string name — there is no schema, no expiry, and no automatic eviction. You own the lifecycle entirely.
self.addEventListener('install', (event) => { event.waitUntil( caches.open('cache-v1').then((cache) => { console.log('Cache opened:', cache); }) );});Multiple caches can coexist. A common pattern is to keep one cache per version of your app so that an old SW and a new SW never share the same cache namespace.
Precaching at install time
Section titled “Precaching at install time”cache.addAll(urls) accepts an array of URL strings and fetches and stores all of them atomically. If any single request fails — network error, 404, or otherwise — the entire addAll call rejects and the install event fails. This is intentional: it guarantees your app shell is either fully cached or not cached at all.
const CACHE_NAME = 'cache-v1';const APP_SHELL = [ '/', '/index.html', '/app.js', '/style.css',];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)) );});Keep the precache list short. Every URL in the list is a mandatory network request at install time — a 404 will prevent the SW from activating.
cache.put and caches.match
Section titled “cache.put and caches.match”For runtime caching you add individual responses using cache.put(request, response). You retrieve them with either cache.match(request) (searches only that specific cache) or caches.match(request) (searches all caches and returns the first match).
// Runtime cache: intercept a fetch, cache the response, return itself.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((cached) => { if (cached) return cached;
return fetch(event.request).then((response) => { // Store a clone; return the original caches.open('cache-v1').then((cache) => { cache.put(event.request, response.clone()); }); return response; }); }) );});// Searching a specific named cache vs. all cachescaches.open('cache-v1').then((cache) => { cache.match('/app.js').then((res) => { console.log('From named cache:', res); });});
caches.match('/app.js').then((res) => { console.log('From any cache:', res);});response.clone()
Section titled “response.clone()”A Response object wraps a body that is a one-time readable stream. Once you consume it — by calling .json(), .text(), or passing it to cache.put() — the stream is drained and the body is gone. Any subsequent read returns an empty result.
When you want to both cache a response and return it to the caller, you must clone it first. response.clone() creates a second Response that shares the same body data but has an independent read cursor.
fetch(event.request).then((response) => { const clone = response.clone(); // independent copy cache.put(event.request, clone); // cache the clone return response; // return the original to the page});Deleting old caches at activate
Section titled “Deleting old caches at activate”When you deploy a new version of your SW with a new cache name, the old caches remain on disk until you explicitly remove them. The activate event is the right place to do this: the new SW has taken over, so it is safe to delete caches that the old SW depended on.
const CACHE_VERSION = 'v1';const CURRENT_CACHES = new Set(['cache-v1', 'images-v1']);
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => !CURRENT_CACHES.has(name)) .map((name) => { console.log('Deleting old cache:', name); return caches.delete(name); }) ); }) );});After cleanup, call self.clients.claim() if you want the newly activated SW to take control of existing open pages immediately rather than waiting for a full page reload.