Skip to content

Quota and persistent storage

How much storage does the browser give you?

Section titled “How much storage does the browser give you?”

Browsers do not give every origin unlimited storage. Instead they allocate a quota — a maximum amount the origin may use — based on available disk space and browser policy. The quota for IndexedDB, Cache API, and OPFS comes from a shared pool called the “default” storage bucket.

The navigator.storage.estimate() API returns a snapshot of how much of that quota your origin has consumed.

const { quota, usage } = await navigator.storage.estimate();
console.log('Quota :', (quota / 1024 / 1024).toFixed(1), 'MB');
console.log('Usage :', (usage / 1024 / 1024).toFixed(2), 'MB');

The returned usage figure covers IndexedDB, Cache API, and OPFS together. It does not include localStorage or cookies, which have separate, smaller limits.

By default, every origin gets best-effort storage. Under low disk space the browser is free to evict best-effort data — silently, without warning — to reclaim room. The eviction order is typically least-recently-used origin first.

You can ask the browser to upgrade your origin to persistent storage. Persistent data is never evicted without an explicit user action (clearing site data). To request it:

const granted = await navigator.storage.persist();
console.log('Persistent storage granted:', granted); // true or false

To check the current state without requesting:

const isPersisted = await navigator.storage.persisted();
console.log('Already persistent:', isPersisted);

Browsers use engagement heuristics to decide whether to grant the request silently or show a permission prompt. Factors that increase the likelihood of an automatic grant include:

  • The site is installed as a PWA (added to Home Screen / installed via browser UI)
  • The user has visited the site frequently
  • The user has bookmarked the site or granted other permissions (push notifications, geolocation)
  • Some browsers (Firefox) always ask the user; Chromium-based browsers may grant silently
flowchart TD
  A[Origin stores data] --> B{Durability tier}
  B -->|Default| C[Best-effort storage]
  B -->|After persist granted| D[Persistent storage]
  C --> E[Browser MAY evict under\ndisk pressure]
  E --> F[Data silently deleted]
  D --> G[Browser will NEVER evict\nwithout user action]
  G --> H[Data safe until user\nclears site data]
Best-effort storage can be evicted silently; persistent storage requires an explicit user action to remove

Runnable: check your quota and persistence state

Section titled “Runnable: check your quota and persistence state”

The snippet below calls both estimate() and persisted() and logs the results. No demo keys are written — the estimate API needs no cleanup.

Browser Storage
OptionBenefitCost
Best-effort storage (default)No permission prompt, works immediatelyThe browser can silently evict data when disk space is low
Persistent storage (persist())Data survives until the user clears site data themselvesDepends on the browser’s engagement heuristics, which you don’t fully control, and some browsers show a permission prompt the user can deny
Calling estimate() before a large writeKnow the remaining headroom, avoid a mid-write QuotaExceededErrorAdds one extra async round-trip before the real operation starts
  • Assuming IndexedDB means “stored forever” — by default every origin only gets best-effort storage. You must call navigator.storage.persist() to actually request an upgrade to persistent storage.
  • Not checking quota before a large write — writing continuously without calling navigator.storage.estimate() first leads to an unexpected QuotaExceededError when disk space runs low.
  • Confusing the usage from estimate() with localStorage — the returned value only covers IndexedDB, Cache API, and OPFS. It does not include localStorage or cookies, which have separate limits.
What does navigator.storage.estimate() return?
Under which condition can the browser silently delete best-effort storage data?
navigator.storage.persist() returns a Promise. What does the resolved value mean?