# StorageManager.estimate()

> What navigator.storage.estimate() returns, how Chrome, Firefox, and Safari size the per-origin quota, and how to budget writes around QuotaExceededError.

`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.

## Syntax

```js
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).

## Parameters

None.

## Return value

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](https://developer.chrome.com/docs/workbox/understanding-storage-quota),
developer.chrome.com).

## How the quota is sized

Each engine derives `quota` from disk size, and the formulas differ ([Storage quotas and
eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/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](https://web.dev/articles/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](https://webkit.org/blog/14403/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.

## Exceptions

| 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.

## Examples

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

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.

```js
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.

### Checking headroom before a large download

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.

```js
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.

### Detecting support and degrading

`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`.

```js
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.

:::observed
Chrome DevTools, Application > Storage, shows the same figures as a usage bar and lets you
force a low ceiling with the **Simulate custom storage quota** checkbox (added in Chrome 88,
[What's New in DevTools (Chrome 88)](https://developer.chrome.com/blog/new-in-devtools-88),
developer.chrome.com). With the override set below the current usage, `estimate()` in that tab
returns the simulated `quota` and the next `cache.put()` rejects with `QuotaExceededError`, which
is the quickest way to exercise the retry path above without filling a disk.
:::

## See also

- [Storage: estimate() method](https://storage.spec.whatwg.org/#dom-storagemanager-estimate) (whatwg.org)
- [Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/) (webkit.org)
- [Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria) (developer.mozilla.org)
- [navigator.storage.persist()](/reference/storage/persistence/)
- [Eviction and best-effort storage](/reference/storage/eviction/)
- [Cache Storage API](/reference/storage/cache-storage/)