skip to content

What does `navigator.storage.estimate()` report in a browser, and what does the `quota` value in its result actually represent?

level: juniorimportance: should knowfreq 42%

answer

  1. a promise, not a property
  2. two numbers come back
  3. one budget for the whole origin
  4. derived from disk, padded on purpose

basics

~20 s

navigator.storage.estimate() resolves with usage and quota in bytes: roughly what the origin already stores, and the approximate ceiling it may reach. Both numbers are deliberately imprecise, and they cover all of the origin's quota-managed storage as one shared pool.

solid answer

~40 s

`navigator.storage` is the `StorageManager`, exposed in secure contexts on both windows and workers. Calling `estimate()` returns a promise that resolves to a `StorageEstimate` object with two byte counts: `usage`, roughly what this origin has already stored, and `quota`, roughly the ceiling it is allowed to reach. The important part is that it is one pool per origin, not a per-API allowance: IndexedDB databases, `CacheStorage` entries and files in the origin private file system all draw on the same budget, so a big video cache shrinks the room left for a database. The quota is derived from disk space, so it differs wildly between a roomy laptop and a nearly-full phone, and it can move between calls. Both numbers are intentionally approximate, so treat the result as a budget signal, never as an accounting ledger.

code

javascript · 7 lines
javascript
async function storageHeadroom() {
  if (!navigator.storage?.estimate) return null;
  const { usage = 0, quota = 0 } = await navigator.storage.estimate();
  return { usage, quota, freeBytes: quota - usage, usedRatio: quota ? usage / quota : 0 };
}

storageHeadroom().then(console.log);

go deeper

for a junior

Know that navigator.storage.estimate() is async and resolves with usage and quota in bytes, and say plainly that both cover the whole origin rather than a single API.

for a middle

Be ready to explain that the quota is computed from disk space rather than fixed, that the storage APIs share one origin pool, and why the values are approximate.

for a senior

Show you use the estimate operationally: prune your own data when headroom drops, log the usage-to-quota ratio from real devices, and never build correctness on an approximate number.

for a principal

Own the budget as a shared resource. Decide which features may write unbounded data, who prunes when the origin fills, and how the team avoids one caching layer starving everything else on constrained devices.

## What the call is `navigator.storage` exposes a `StorageManager`. It is available only in secure contexts (HTTPS, or `localhost` during development) and it exists both on `window` and inside workers. Its `estimate()` method returns a promise that resolves to a `StorageEstimate` — a plain dictionary with two members, `usage` and `quota`, each a byte count. ```js const { usage, quota } = await navigator.storage.estimate(); console.log(`${usage} bytes used of about ${quota}`); ``` There is no synchronous property to read instead. Measuring how much an origin stores means walking real on-disk structures, so the platform makes it asynchronous. ## One pool, not per-API budgets The single most common misreading is to think each storage API has its own allowance. It does not. The quota belongs to the origin, and the quota-managed APIs share it: IndexedDB databases, entries written through the Cache API's `CacheStorage`, files created under the origin private file system reached via `navigator.storage.getDirectory()`, and the resources a service worker registration holds. Web Storage (`localStorage`) is accounted for too, though in practice it is separately pinned to a small per-origin cap of a few megabytes, so it is never the thing that consumes the pool. The practical consequence: if a caching layer parks 400 MB of media, that is 400 MB the database cannot have. Any component that writes without bound is spending a budget the whole origin shares. ## Where the quota number comes from Quota is not a constant baked into the browser. It is computed from the device's disk. Chromium documents an origin ceiling of roughly 60% of total disk space; Firefox computes a global limit from available disk and then caps what any one site's group may take. The numbers therefore differ by browser, by device, and over time on the same device — as the user's disk fills, the reported quota falls. Private or incognito windows report a much smaller quota, and everything written there is discarded when the session ends. That variability is the answer to "why does this work on my machine and fail for that user": nothing about the app changed, the ceiling did. ## Why the numbers are approximate Browsers deliberately blur both values, for two distinct reasons. The first is privacy. If `usage` were exact, a page could cache a cross-origin resource and read the size difference, learning something about a response it is not otherwise allowed to inspect. Chromium counters this by padding opaque cross-origin responses stored through the Cache API, so their accounted size does not match their real size. Exact quota values are also a fingerprinting surface, since disk size is fairly identifying. The second is bookkeeping. Stored bytes are not the bytes you handed the API: indexes, per-record metadata, and block-level rounding all add overhead. A 1 KB record does not consume 1 KB. So `usage` will normally read larger than the sum of what you wrote, and it should never be asserted on exactly in a test. Some Chromium builds add a non-standard `usageDetails` member that breaks usage down by API. It is useful while debugging, but it is not in the specification and must not be relied on in shipped logic. ## Using the estimate well Good uses are all comparative and coarse: ```js const { usage = 0, quota = 0 } = await navigator.storage.estimate(); const headroom = quota - usage; if (headroom < 50 * 1024 * 1024) { await pruneOldestCaches(); } ``` Check for headroom before a large import or an offline sync; prune your own least valuable data when the origin is getting full; surface a warning before a download that obviously will not fit; log the ratio so you can see how close real users run to their ceiling. Bad uses are anything that treats the number as authoritative: showing users an exact "you are using 12.4 MB" figure, computing a precise per-feature breakdown, or gating correctness on the value. ## The thing it does not tell you A generous quota says nothing about safety. Quota governs how much you may write; whether what you wrote survives is a separate mechanism — best-effort storage can be evicted long before an origin approaches its ceiling, and clearing browser data removes it regardless. Reading a large quota and concluding "there is plenty of room, so the data is fine" conflates two unrelated guarantees.

  • If estimate() reports a huge quota, does that mean the data you wrote is safe?
    No. Quota is a ceiling on how much you may write; survival is governed by eviction, which is a separate mechanism. Best-effort storage can be deleted under disk pressure long before the origin approaches its quota, and a user clearing site data removes it whatever the quota says. Headroom is a write budget, not a durability guarantee.
  • Why is the reported usage usually larger than the total bytes your code wrote?
    Accounting overhead and deliberate padding. Databases add index and per-record metadata, storage engines round to block boundaries, and Chromium pads opaque cross-origin responses stored via the Cache API so their accounted size does not reveal the real response size. Expect the reported figure to exceed your own sum, and never assert on it exactly.
  • Does estimate() work inside a service worker?
    Yes. StorageManager is exposed on WorkerGlobalScope as well as Window, so a service worker can await navigator.storage.estimate() before deciding whether to cache a large payload. The same secure-context requirement applies, which service workers already satisfy since they only run on HTTPS or localhost.

saying these in an interview costs you the question

  • Says every origin gets a fixed 5 MB and that is the quota
  • Treats usage as an exact byte count to display or assert
  • Assumes IndexedDB and the Cache API have separate quotas
  • Reads navigator.storage.quota as a synchronous property
  • Concludes data is safe from deletion because quota is large

context