Skip to content

Cache Storage API — Storing and Retrieving Network Responses

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Cache Storage API. We cover key concepts, practical examples, and best practices to help you master this topic.

The Cache Storage API stores HTTP Request/Response pairs in the browser, enabling offline access, faster loads, and programmable Caching strategies for Progressive Web Apps.

What You'll Learn

By the end of this tutorial, you will understand how to open caches, add and retrieve responses, delete entries, and manage cache storage limits programmatically.

Why It Matters

The Cache Storage API is the foundation of all PWA caching strategies. Without understanding how it works, you cannot debug cache issues, optimize storage usage, or implement advanced strategies like cache pruning or quota management.

Real-World Use

A news PWA uses multiple caches: a static cache for the app shell, a dynamic cache for article responses, and an images cache. Each cache has a size limit. When the images cache exceeds 50 entries, the oldest 10 are evicted. The user never sees stale images or storage warnings.

What Is Cache Storage

Cache Storage is a browser storage mechanism specifically designed for HTTP Request/Response objects. It is different from localStorage (which stores strings) and IndexedDB (which stores structured data).

Cache Storage vs Other Storage
    ┌──────────────────────────────────────────────────────────┐
    │              Browser Storage Options                     │
    ├────────────┬──────────────┬──────────────┬──────────────┤
    │            │ Cache API    │ IndexedDB    │ localStorage │
    ├────────────┼──────────────┼──────────────┼──────────────┤
    │ Data       │ HTTP pairs   │ Structured   │ Strings      │
    │ Size       │ Large        │ Large        │ ~5-10MB      │
    │ Async      │ Yes          │ Yes          │ No           │
    │ Indexed    │ By URL       │ By key/index │ By key       │
    │ Expiry     │ Manual       │ Manual       │ Manual       │
    │ Use case   │ Offline      │ App data     │ Preferences  │
    └────────────┴──────────────┴──────────────┴──────────────┘

Think of Cache Storage like a filing cabinet specifically designed for network responses. Each drawer (cache) is labeled, and each file (response) is organized by URL (request). You open a drawer, find a file by URL, read it, or put a new file in.

Opening and Creating Caches

// Open a cache (creates if it does not exist)
caches.open('my-cache-v1').then(cache => {
    console.log('Cache opened:', cache);
});

// Check if a cache exists
caches.has('my-cache-v1').then(exists => {
    console.log('Cache exists:', exists);
});

// List all caches
caches.keys().then(names => {
    console.log('All caches:', names);
});

// Delete a cache
caches.delete('old-cache').then(deleted => {
    console.log('Cache deleted:', deleted);
});

Output:

Cache opened: [object Cache]
Cache exists: true
All caches: ['my-cache-v1', 'my-cache-v0']
Cache deleted: true

Adding Responses to Cache

There are three ways to add responses to a cache:

// Method 1: cache.add(url) — fetches the URL and caches the response
caches.open('dynamic-cache').then(cache => {
    cache.add('/api/data.json').then(() => {
        console.log('Fetched and cached: /api/data.json');
    });
});

// Method 2: cache.addAll(urls) — fetches and caches multiple URLs
caches.open('static-cache').then(cache => {
    cache.addAll([
        '/styles/main.css',
        '/scripts/app.js',
        '/images/logo.png'
    ]).then(() => {
        console.log('All static assets cached');
    });
});

// Method 3: cache.put(request, response) — stores an existing response
caches.open('dynamic-cache').then(cache => {
    fetch('/api/users').then(response => {
        if (response.ok) {
            cache.put('/api/users', response.clone());
            console.log('API response cached');
        }
    });
});

Output:

Fetched and cached: /api/data.json
All static assets cached
API response cached

Retrieving Responses

// Match a single request
caches.open('my-cache').then(cache => {
    cache.match('/styles/main.css').then(response => {
        if (response) {
            console.log('Found in cache:', response.url);
            console.log('Status:', response.status);
        } else {
            console.log('Not in cache');
        }
    });
});

// Match across all caches
caches.match('/styles/main.css').then(response => {
    if (response) {
        console.log('Found in any cache');
    }
});

// Get all cached requests
caches.open('my-cache').then(cache => {
    cache.keys().then(requests => {
        console.log(`Cache has ${requests.length} entries:`);
        requests.forEach(request => {
            console.log(' -', request.url);
        });
    });
});

Output:

Found in cache: https://example.com/styles/main.css
Status: 200
Found in any cache
Cache has 15 entries:
 - https://example.com/
 - https://example.com/styles/main.css
 - https://example.com/scripts/app.js
...

Cache Quota Management

Browsers limit how much storage a single origin can use. You should monitor and manage your cache size:

// Check storage usage
async function checkStorageQuota() {
    if ('storage' in navigator && 'estimate' in navigator.storage) {
        const estimate = await navigator.storage.estimate();
        const usageMB = (estimate.usage / (1024 * 1024)).toFixed(2);
        const quotaMB = (estimate.quota / (1024 * 1024)).toFixed(2);
        const percent = ((estimate.usage / estimate.quota) * 100).toFixed(1);

        console.log(`Storage: ${usageMB}MB / ${quotaMB}MB (${percent}%)`);
        return { usage: estimate.usage, quota: estimate.quota, percent };
    }
}

checkStorageQuota().then(info => {
    if (info && info.percent > 80) {
        console.warn('Storage almost full, evicting old caches');
        evictOldestCache();
    }
});

// Evict the oldest cache when storage is low
function evictOldestCache() {
    caches.keys().then(names => {
        // Find the oldest cache by name convention (e.g., cache-v1, cache-v2)
        const sorted = names.sort();
        const oldest = sorted[0];
        if (oldest) {
            caches.delete(oldest).then(() => {
                console.log('Evicted:', oldest);
            });
        }
    });
}

Output:

Storage: 12.34MB / 100MB (12.3%)
Storage: 85.67MB / 100MB (85.7%)
Storage almost full, evicting old caches
Evicted: images-v1

Cache Entry Management

// Delete a specific entry from a cache
caches.open('dynamic-cache').then(cache => {
    cache.delete('/api/stale-data.json').then(deleted => {
        console.log('Entry deleted:', deleted);
    });
});

// Prune cache to a maximum number of entries
async function pruneCache(cacheName, maxEntries) {
    const cache = await caches.open(cacheName);
    const keys = await cache.keys();

    if (keys.length > maxEntries) {
        const toDelete = keys.length - maxEntries;
        console.log(`Pruning ${toDelete} entries from ${cacheName}`);

        // Delete oldest entries (first in array)
        for (let i = 0; i < toDelete; i++) {
            await cache.delete(keys[i]);
        }
    }
}

// Prune when cache grows too large
pruneCache('dynamic-cache', 50);

Output:

Entry deleted: true
Pruning 10 entries from dynamic-cache

Cache Strategies with Cache API

Understanding Cache Storage lets you implement any caching Strategy:

// Stale-while-revalidate using Cache API directly
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.open('dynamic-cache').then(cache => {
            return cache.match(event.request).then(cachedResponse => {
                const fetchPromise = fetch(event.request).then(networkResponse => {
                    // Update cache with fresh response
                    cache.put(event.request, networkResponse.clone());
                    return networkResponse;
                }).catch(() => {
                    // Network failed, return cached version
                    return cachedResponse;
                });

                // Return cached immediately, or wait for network if no cache
                return cachedResponse || fetchPromise;
            });
        })
    );
});

Common Mistakes

  1. Using cache.match() instead of cache.match(request). cache.match() without arguments throws an error. Always pass a Request or URL string.
  2. Not cloning responses before caching. Response bodies are streams that can only be read once. Always call response.clone() before passing to cache.put().
  3. Forgetting to handle cache misses. cache.match() returns undefined if no match. Always check if the response exists before using it.
  4. Not checking response.ok before caching. Cache error responses (4xx, 5xx) will serve broken content. Cache only successful responses.
  5. Mixing cache.put() with cache.add() semantics. cache.add() fetches the URL. cache.put() stores a pre-fetched response. Do not use them interchangeably.

Practice Questions

  1. What is the difference between cache.add() and cache.put()?
  2. How do you check how much storage your caches are using?
  3. What happens when you exceed browser storage quota?
  4. Why must you clone a response before caching it?
  5. How do you delete a single entry from a cache?

Challenge: Write a function that monitors cache storage and automatically evicts the oldest 10% of entries when usage exceeds 80% of the quota. Test by adding many entries programmatically.

FAQ

How much data can I store in the Cache API?

Chrome allows up to 60% of available disk space per origin (capped at ~2GB). Firefox and Safari have similar limits. The exact limit depends on the device and available space.

Is Cache Storage persistent?

Cache Storage is persistent by default on modern browsers. The browser does not clear caches unless storage pressure triggers eviction. Users can clear site data manually.

Can I cache responses with headers?

Yes, the Cache API stores the complete Response object including headers, status code, and body. Headers are preserved when serving from cache.

Does the Cache API work in Web Workers?

Yes, the Cache API is available in service workers, dedicated workers, and window context. However, it is most commonly used in service workers for offline support.

Can I cache POST requests?

Technically yes, but the Cache API uses the Request object as the key, including the method and body. Matching POST requests is tricky because the body must match exactly.

Mini Project

Create a cache manager service worker that maintains three caches: static-v1 (for app shell), dynamic-v1 (for API responses, max 30 entries), images-v1 (for images, max 50 entries). Implement functions for each cache type: add, retrieve, delete, and prune. Log all operations to the console.

What's Next

You understand Cache Storage. Now explore the cache-first strategy, which serves cached content instantly and only falls back to the network when needed.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro