cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Cache API & file storage

Store whole responses with the Cache API, inspect browser storage quota, request persistence, and write private origin files with OPFS.

You will learn to
  • 01
    Store responses deliberatelyUse CacheStorage and named Cache objects for Request to Response pairs.
  • 02
    Read storage limits honestlyInspect approximate quota, usage, eviction, and persistence decisions.
  • 03
    Write private filesUse OPFS handles for origin-private folders and files when the browser supports them.

Responses, quota, and private files

Browser storage is not one toolbox. It is a set of very different tools. The Cache API stores whole Request → Response pairs. The Storage Manager tells you roughly how much space your origin is using and whether it has persistent storage. The Origin Private File System, usually shortened to OPFS, gives your site private file and folder handles.

Those APIs are useful when an app should feel fast or keep working offline: documentation readers, drawing tools, recipe apps, music editors, dashboards, and installable web apps. They are also easy to misuse if you treat all storage as “a place to put stuff.” In this lesson we choose storage by the shape of the data and the lifetime you need.

Real-life analogyThe Cache API is a pantry of ready meals

You can cook once, store the whole meal, and grab it later. The network is the store. A named Cache is the shelf. You decide what goes in and when stale entries leave.

In real life: Ready meal
In JavaScript: A complete Response including headers and body
In real life: Label on the container
In JavaScript: The Request or URL used as the key
In real life: Pantry shelf
In JavaScript: A named Cache opened with caches.open()
In real life: Throwing out old food
In JavaScript: Your versioning and cleanup code

Where the analogy stops: A pantry does not have streaming response bodies or browser quota. Also, unlike the automatic HTTP cache, the Cache API never expires entries just because time passed.

Definition: CacheStorage is the browser’s collection of named Cache objects; each Cache maps requests to responses. OPFS is a private file area for the same origin. Quota is the browser’s estimate of how much room that origin may use.

The Cache API

MODEL

The global caches object is a CacheStorage. It is available in windows and workers in supporting secure contexts; http://localhost counts as secure for development. You call caches.open(name) to get a named Cache, then use methods such as put, add, match, keys, and delete.

Cache API vs the browser's HTTP cache
QuestionCache APIHTTP cache
Who controls entries?Your JavaScript code opens named caches and writes entries.The browser follows request and response cache rules.
What is stored?Request to Response pairs you choose.Network responses the browser decides are reusable.
ExpirationNo automatic expiration. Use versioned cache names and delete old entries.Headers such as Cache-Control guide reuse and revalidation.
Typical userService workers and offline-capable app code.Every page load and subresource request can use it automatically.
Common Cache API operationsJavaScript
const cache = await caches.open("cf-demo-pantry");await cache.put(  "/cf-demo/menu.json",  new Response(JSON.stringify(menu), {    headers: { "Content-Type": "application/json", "X-Saved-At": savedAt },  }),);await cache.add(location.href);const match = await cache.match("/cf-demo/menu.json?from=button", { ignoreSearch: true });const allKeys = await cache.keys();await cache.delete("/cf-demo/menu.json");

The value is a real Response. Headers come with it. Bodies are streams, so if you fetched a response and also want to read it in your code, clone it before one copy is consumed. Opaque responses can be cached too, but scripts cannot inspect their contents and browsers may charge padded quota space for privacy.

The Cache API is not a database

It is excellent for replaying responses. It is awkward for searching records, partial updates, and queries. Structured records belong in IndexedDB; file-shaped drafts belong in OPFS or IndexedDB.

Pantry playground

INTERACTIVE

This playground uses the real Cache API when your browser exposes caches. It creates a demo cache named cf-demo-pantry, stores a synthetic JSON response, stores this same-origin page with cache.add(location.href), matches with ignoreSearch, lists keys, proves caches.match searches across named caches, and deletes the demo data.

Pantry playground: real Cache API
Pantry operationsJavaScript
const cache = await caches.open("cf-demo-pantry");await cache.put(  "/cf-demo/menu.json",  new Response(JSON.stringify(menu), {    headers: { "Content-Type": "application/json", "X-Saved-At": savedAt },  }),);await cache.add(location.href);const match = await cache.match("/cf-demo/menu.json?from=button", { ignoreSearch: true });const allKeys = await cache.keys();await cache.delete("/cf-demo/menu.json");
Pantry logcf-demo-pantry
  1. 1Press a button to use the real Cache API in this browser.
Try it yourself

The cache name starts with cf-demo and Reset deletes it. CacheStorage usually requires a secure context; localhost counts.

This experiment uses real browser APIs and same-origin resources only.

Reset deletes the demo cache. The component also deletes it on unmount. That cleanup matters: educational demos should not leave mystery storage behind.

Cache-first and stale refresh

STEP THROUGH

Service workers often pair the Cache API with request strategies. You can use the same ideas in app code: try the cache first, fall back to the network, or serve something cached while refreshing it for the next visit.

A small getMenu strategyPop out in the code editor (opens in a new tab)JavaScript
async function getMenu(cache, network, mode) {  const cached = cache.match("/menu.json");   if (cached && mode === "cache-first") {    return { source: "cache", menu: cached.menu };  }   if (cached && mode === "stale-while-revalidate") {    network.fetch("/menu.json").then((fresh) => {      cache.put("/menu.json", fresh);    });    return { source: "cache", menu: cached.menu };  }   const fresh = await network.fetch("/menu.json");  cache.put("/menu.json", fresh);  return { source: "network", menu: fresh.menu };}
Step through getMenu
Step 0 of 3Ready
Your turn: follow the blue line

Switch strategy, then step through how cached data changes the control flow.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  const cached = cache.match("/menu.json");   if (cached && mode === "cache-first") {    return { source: "cache", menu: cached.menu };  }   if (cached && mode === "stale-while-revalidate") {    network.fetch("/menu.json").then((fresh) => {      cache.put("/menu.json", fresh);    });    return { source: "cache", menu: cached.menu };  }   const fresh = await network.fetch("/menu.json");  cache.put("/menu.json", fresh);  return { source: "network", menu: fresh.menu };}
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose a strategy

Changing the strategy starts a fresh pure-model replay. Node tests prove this does not depend on a browser Cache API.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Cache-first is great for versioned files and app shells where cached is usually correct. Stale-while-revalidate is great when speed matters and yesterday’s data is acceptable for a moment. It is a poor fit for bank balances, checkout prices, or anything where old data can harm the user.

Storage quotas and persistence

INTERACTIVE
Real-life analogyQuota is the size of your apartment

Your site gets room based on the device and browser policy. Asking for persistence is like asking the landlord not to clear your things when the building is crowded.

In real life: Apartment size
In JavaScript: The origin's available quota
In real life: Stuff already inside
In JavaScript: Current usage
In real life: Landlord promise
In JavaScript: Persistent storage request
In real life: Clearing space
In JavaScript: Eviction or user-cleared site data

Where the analogy stops: Browsers do not promise a fixed number of bytes forever. Quota is approximate, varies by browser and disk conditions, and is deliberately imprecise for privacy.

navigator.storage.estimate() returns approximate usage and quota. Some browsers include usageDetails. navigator.storage.persisted() reports whether the origin is already persistent, and persist() asks for persistence. Depending on the browser, that request may be granted automatically, prompt the user, or be denied.

Storage Manager callsJavaScript
const estimate = await navigator.storage.estimate();const persisted = await navigator.storage.persisted();// Only ask after a user click; some browsers prompt here.const granted = await navigator.storage.persist();
Storage quota panel
Storage Manager callsJavaScript
const estimate = await navigator.storage.estimate();const persisted = await navigator.storage.persisted();// Only ask after a user click; some browsers prompt here.const granted = await navigator.storage.persist();
Quota logStorageManager
  1. 1Press Estimate to ask the Storage Manager for approximate usage and quota.
Try it yourself

Quota numbers are approximate and can change. Persistence may be granted automatically, prompt, or be denied depending on the browser.

No prompt is requested until you press the persistence button.

Persistence reduces automatic eviction under storage pressure; it is not a backup system. Users can still clear site data, private browsing can be temporary, and your app should rebuild caches when needed.

The Origin Private File System

INTERACTIVE
Real-life analogyOPFS is a private hard-drive folder for your site

OPFS gives your app file-shaped storage without asking the user where to save each internal file. That is useful for drafts, media chunks, local project files, and generated assets.

In real life: Private folder
In JavaScript: The origin's OPFS root
In real life: Subfolders
In JavaScript: getDirectoryHandle(name, { create: true })
In real life: Text or binary files
In JavaScript: getFileHandle, createWritable, getFile()
In real life: Locked door
In JavaScript: Only the same origin can see it

Where the analogy stops: It is not the user's normal Downloads folder. The files are private to the origin and can be removed when site data is cleared. Browser support and main-thread writable handles vary.

The entry point is navigator.storage.getDirectory(), which returns the OPFS root FileSystemDirectoryHandle. In supporting browsers you can create a file, call createWritable(), write text or bytes, close the stream, read with getFile().text(), iterate entries, and delete with removeEntry. Sync access handles are for dedicated workers; this page demonstrates the main-thread writable flow only when available.

OPFS write, read, list, deleteJavaScript
const root = await navigator.storage.getDirectory();const demo = await root.getDirectoryHandle("cf-demo", { create: true });const file = await demo.getFileHandle("menu-note.txt", { create: true });const writable = await file.createWritable();await writable.write("Cache pantry inspected at lunch.");await writable.close();const text = await (await file.getFile()).text();for await (const [name, handle] of demo.entries()) {  console.log(name, handle.kind);}await root.removeEntry("cf-demo", { recursive: true });
OPFS lab
OPFS write, read, list, deleteJavaScript
const root = await navigator.storage.getDirectory();const demo = await root.getDirectoryHandle("cf-demo", { create: true });const file = await demo.getFileHandle("menu-note.txt", { create: true });const writable = await file.createWritable();await writable.write("Cache pantry inspected at lunch.");await writable.close();const text = await (await file.getFile()).text();for await (const [name, handle] of demo.entries()) {  console.log(name, handle.kind);}await root.removeEntry("cf-demo", { recursive: true });
File logcf-demo
  1. 1Press Write file to try OPFS in this browser.
Try it yourself

Files live under a private cf-demo folder for this origin and are removed on Reset and unmount.

Feature detection keeps the lab honest when a browser lacks OPFS or main-thread writable handles.

Which storage should I choose?

SORT

The fastest way to choose is to ask what shape the data has. Is it a response? A private file? A tiny string preference? A queryable collection? A credential JavaScript should not read?

Storage choice helperPop out in the code editor (opens in a new tab)JavaScript
function chooseStorage(need) {  if (need === "offline app shell") return "Cache API";  if (need === "large private file") return "OPFS or IndexedDB";  if (need === "small UI preference") return "localStorage";  if (need === "queryable records") return "IndexedDB";  return "HttpOnly cookie";}
Which storage?
  • Offline app shell: HTML, CSS, icons, JSON responses
  • A same-origin avatar Response you want offline
  • Large private draft video or drawing file
  • Thousands of records you need to search by index
  • Theme = dark
  • Temporary wizard step for this tab
  • Session token that JavaScript should not read
  • Server credential sent automatically with same-site requests
Try it yourself
0 of 8 correct

Sort each real app need into the storage option that fits best.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Storage choices at a glance
NeedGood fitWhy
Offline app shell or replayable responseCache APIStores whole responses with headers.
Large drafts or binary app filesOPFS or IndexedDBPrivate storage designed for larger app data.
Small UI preferencelocalStorageSimple string key-value data.
Structured queryable recordsIndexedDBObject stores, indexes, and transactions.
Session tokenHttpOnly cookieJavaScript cannot read it.

Where you will use this

REAL APPS

Imagine a restaurant menu editor. The public app caches the menu JSON and shell files so repeat visitors see the menu instantly. The editor estimates quota before saving large image drafts. Draft notes and generated images live in OPFS under a project folder. The user’s theme choice is just a tiny localStorage value. Their login token belongs in an HttpOnly cookie, not in script-readable storage.

Offline apps are a system

A service worker can answer requests from the Cache API while the page is offline. The page can use IndexedDB or OPFS for app data. The Service workers & offline apps lesson covers the interception part; this lesson focuses on the storage APIs themselves.

Professional code also includes cleanup. Name caches with versions, remove old versions, cap large OPFS projects, and rebuild derived caches if they disappear. Treat browser storage as helpful local state, not your only copy of critical user data.

Common misconceptions

WATCH OUT
  • “The Cache API is the HTTP cache.” They are separate. Your code manages named Cache objects.
  • “Cache API entries expire by themselves.” They do not. Use versions and cleanup.
  • “A cached Response can be read forever.” Stored responses can be matched again, but each Response body stream you hold is still one-use. Clone before two reads.
  • “Quota is guaranteed storage.” It is approximate and can change. Non-persistent data may be evicted.
  • “persist() always shows a permission prompt.” Browsers vary: automatic grant, prompt, or denial are all possible.
  • “OPFS files show up in the user’s file browser.” They are private to the origin and are removed with site data.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upRead a cached header

Predict the header value printed by this cache-like Map.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const response = new Response("hello", { headers: { "X-Saved-At": "noon" } });
const cache = new Map();
cache.set("/menu", response.clone());
console.log(cache.get("/menu").headers.get("X-Saved-At"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeClone before two reads

    What two lines print after cloning the Response?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const response = new Response("one-use body");
    const copy = response.clone();
    response.text().then((text) => console.log(text));
    copy.text().then((text) => console.log(text));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the expiration bug

      A teammate says, “The Cache API will remove old entries after a day.” Who actually owns that cleanup?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeName the quota API

        Which method estimates origin storage usage and quota?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeOpen the private file root

          Which method opens the OPFS root directory?

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Cache API & file storage quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What does a named Cache store?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const response = new Response("hello", { headers: { "X-Saved-At": "noon" } });
              console.log(response.headers.get("X-Saved-At"));

              Choose an answer to see the explanation.

            3. Question 3 of 8Which statement about Cache API expiration is true?

              Choose an answer to see the explanation.

            4. Question 4 of 8What does the Cache API availability check print in Node tests?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(typeof caches === "undefined" ? "no Cache API" : "Cache API available");

              Choose an answer to see the explanation.

            5. Question 5 of 8What does navigator.storage.estimate() return?

              Choose an answer to see the explanation.

            6. Question 6 of 8Which API opens the OPFS root?

              Choose an answer to see the explanation.

            7. Question 7 of 8Why clone a Response before caching it and reading it?

              Choose an answer to see the explanation.

            8. Question 8 of 8Which storage fits a secret session token best?

              Choose an answer to see the explanation.

            Key takeaways

            • The Cache API stores named Request → Response pairs and does not expire them automatically.
            • Clone responses before caching and reading when two consumers need the same body.
            • navigator.storage.estimate() reports approximate usage and quota; persist() asks for reduced eviction risk.
            • OPFS gives private origin files and folders through handles, with browser support and cleanup responsibilities to check.
            • Choose storage by data shape: responses, files, tiny preferences, records, or credentials.

            Final definition: Cache API stores replayable responses, Storage Manager describes the origin’s space, and OPFS stores private files for apps that need file-shaped data.

            Up next: Observers — React to visibility, DOM, and size changes efficiently.

            CompleteFrontend Clear concepts. Working examples.