Skip to content

Storage · API

StorageManager.estimate()

Published

navigator.storage.estimate() resolves to the number of bytes this origin already stores (usage) and a conservative ceiling on how many it may store (quota). The ceiling is a share of the disk computed by the browser at call time, not a constant, so it is read at runtime rather than hardcoded.

navigator.storage.estimate()

The method takes no arguments and returns a Promise that fulfils with a StorageEstimate dictionary. It is available in windows and in workers (self.navigator.storage inside a service worker), and only in secure contexts (BCD api.StorageManager.estimate: Chrome 61, Firefox 57, Safari 17).

None.

The promise fulfils with an object carrying two standard members and one Chromium extension.

Member Type Meaning
usage number Bytes the origin occupies across the quota-managed stores (Cache Storage, IndexedDB, OPFS, service worker registrations). Rounded and padded, so not an exact ledger.
quota number Bytes the origin may use in total. A conservative estimate that shrinks as the disk fills.
usageDetails object Chromium only (BCD api.StorageManager.estimate.usageDetails). A breakdown of usage by storage system; stores with zero usage are omitted, so test for each key before reading it.

The Storage Standard lets the browser obfuscate both numbers with compression, deduplication, and padding so that a page cannot infer disk size or another site’s data from exact byte counts. Chromium’s padding of opaque responses is the most visible case: a cross-origin no-cors response of a few kilobytes contributes roughly 7 MB to usage (Understanding storage quota, developer.chrome.com).

Each engine derives quota from disk size, and the formulas differ (Storage quotas and eviction criteria, developer.mozilla.org).

Engine Per-origin quota Ceiling across all origins
Chromium (Chrome, Edge) 60 % of total disk size, best-effort or persistent alike. Incognito reduces this to about 5 %, and the “Clear cookies and site data when you close all windows” setting caps it near 300 MB (Storage for the web, web.dev). 80 % of total disk
Firefox The smaller of 10 % of total disk and the 10 GiB group limit shared by all origins of one site (eTLD+1). Persistent origins may use 50 % of disk, capped at 8 TiB, outside the group limit. 50 % of free disk
WebKit (Safari 17, iOS 17, macOS 14) About 60 % of total disk in a browser app, about 15 % inside another app’s WKWebView; a cross-origin frame gets 10 % of the embedding origin’s quota. A Home Screen or Dock web app keeps the browser figure (Updates to Storage Policy, webkit.org). 80 % of disk in a browser app, 20 % in other apps

Before Safari 17 an origin started with 1 GiB and Safari asked the user before granting more, in 200 MB steps. The quota an older WebKit reports therefore moves after a prompt, which is one more reason to re-read it before each large write rather than caching it.

Exception When
TypeError The browser cannot obtain a storage shelf for the origin: the origin is opaque (a sandboxed <iframe> without allow-same-origin, a data: document) or the user has disabled storage for the site. The promise rejects; the method itself does not throw.

Running out of room is not an exception of estimate(). Writes that exceed quota fail in the store that performed them: cache.put() rejects with a QuotaExceededError DOMException, and an IndexedDB request fires error with request.error.name === "QuotaExceededError" and aborts its transaction.

The examples read the estimate for display, use it to decide before a large write, and handle its absence.

Reading the estimate and showing a budget line

Section titled “Reading the estimate and showing a budget line”

The two members are enough to show how full the origin is. Divide before formatting, because both values are bytes and quota can exceed 2^53 bytes only on absurdly large disks, so plain floating-point arithmetic is fine.

const { usage, quota } = await navigator.storage.estimate();
const percent = ((usage / quota) * 100).toFixed(1);
console.log(`${(usage / 1048576).toFixed(1)} MiB of ${(quota / 1073741824).toFixed(1)} GiB (${percent} %)`);

Expect quota to be hundreds of gigabytes on a desktop with Chromium’s 60 % rule and far less on a phone that is nearly full; a budget that fits one device will not fit another.

Call estimate() immediately before a write you know the size of, then keep a margin for the padding described above. If the write still fails, catch QuotaExceededError, free space, and retry once instead of failing the whole operation.

async function cacheLargeAsset(cache, url, expectedBytes) {
const { usage, quota } = await navigator.storage.estimate();
if (quota - usage < expectedBytes * 1.2) {
await pruneOldEntries(cache);
}
try {
await cache.add(url);
} catch (err) {
if (err.name !== 'QuotaExceededError') throw err;
await pruneOldEntries(cache);
await cache.add(url);
}
}

The 20 % margin is a working allowance for rounding, not a figure from any specification; the retry after pruning is the part that matters, because a quota read a second earlier is already stale on a device that is also downloading photos.

navigator.storage is absent on insecure origins and in browsers older than those listed in the Syntax section. Treat a missing method as “no budget information” and write optimistically, relying on the store’s own QuotaExceededError.

async function storageBudget() {
if (!('storage' in navigator) || typeof navigator.storage.estimate !== 'function') {
return null; // unknown budget: fall back to catching the quota error on write
}
try {
return await navigator.storage.estimate();
} catch (err) {
if (err.name === 'TypeError') return null; // opaque origin or storage disabled
throw err;
}
}

A null result keeps the calling code on one path: it attempts the write and handles the failure, which it must do anyway because the estimate is advisory.

Specifications

SpecificationStatus
None.