X

Understanding Browser Storage Quotas and Eviction

Browser storage is useful for preferences, offline records, and cached files, but it is not unlimited disk space. A write can fail when an origin reaches its quota, and a browser can remove stored data later. If an app says a change is saved before the write succeeds, a reader may lose work without knowing why.

The safest approach is to choose a storage API for the kind of data you have, treat quota estimates as guidance rather than guarantees, handle failed writes, and keep another copy of important user data.

What counts toward browser storage?

Most browser-managed storage is tracked per origin: the combination of a scheme, hostname, and port. For example, pages under https://example.com generally share an origin's storage, while http://example.com and https://example.com do not. Browsers may further partition storage in embedded third-party contexts, so an iframe should not assume it shares the same storage area as a top-level visit.

The browser's storage budget can cover several APIs at once:

API A good fit Quota consideration
localStorage A small amount of string-based state, such as display preferences It is synchronous and limited. MDN documents up to 5 MiB each for local and session storage per origin.
sessionStorage Temporary, tab-scoped values It is also limited to about 5 MiB per origin and is cleared when the page session ends.
IndexedDB Structured application records, larger objects, and offline data The browser chooses the quota; there is no single cross-browser limit to build against.
Cache API Request and response pairs, such as files used by an offline app shell Cache data shares browser-managed storage limits with other APIs and can be evicted.

Use localStorage for small settings, not a large draft or database. It stores strings and blocks the main thread while it reads or writes. IndexedDB is asynchronous and designed for larger structured data. The Cache API is for cached responses; it is not a durable backup for user-created content.

These APIs do not each get a fixed, independent allowance. Browser limits vary with the browser, device, available space, and storage policy. Avoid code or product copy that promises a specific amount of IndexedDB or Cache storage to every user.

Quota and eviction are different problems

A quota is a limit on how much storage the browser will allow. When a write would exceed the available limit, the operation can fail with a QuotaExceededError.

Eviction is the browser removing previously stored data. Most website storage is best-effort by default: it can remain available for a long time, but the browser may remove it under storage pressure or when the user clears site data. Browser policies differ. Safari also has proactive eviction rules in some contexts, so infrequently used sites should not assume every cached or locally stored value will remain indefinitely.

When a browser evicts an origin, it may remove all of that origin's browser-managed data together, including both IndexedDB records and Cache API entries. Do not treat a cache as a safety copy of a database stored on the same origin.

Private browsing is another reason not to promise persistence. Browsers may apply different quotas in private sessions, and stored data is usually removed when the session ends.

Estimate usage without relying on a fixed limit

The Storage API can report an approximate usage and quota for the current origin. This is useful for a storage-status display or to inform a user before a large optional download. It is not a promise that the remaining estimate will be available: values are approximate, may be padded for privacy, and can change before the next write.

async function getStorageEstimate() {
  if (!navigator.storage?.estimate) {
    return null;
  }

  try {
    const { usage, quota } = await navigator.storage.estimate();
    if (typeof usage !== "number" || typeof quota !== "number" || quota <= 0) {
      return null;
    }

    return {
      usageBytes: usage,
      quotaBytes: quota,
      approximateRemainingBytes: Math.max(0, quota - usage),
    };
  } catch (error) {
    console.error("Could not estimate browser storage", error);
    return null;
  }
}

navigator.storage.estimate() is available in secure contexts such as HTTPS. If the API is unavailable or the estimate fails, keep the feature usable and report that the estimate is unavailable. Do not use an estimate as the only check before a write; handle the write's actual result too.

Handle a full storage area when saving

For localStorage, catch the quota exception and let the interface distinguish an unsaved change from a saved one:

function saveDraft(draft) {
  try {
    localStorage.setItem("draft", JSON.stringify(draft));
  } catch (error) {
    if (error instanceof DOMException && error.name === "QuotaExceededError") {
      return { saved: false, reason: "storage-full" };
    }

    throw error;
  }

  return { saved: true };
}

The caller should show an explicit unsaved message when saved is false, keep the current text in memory, and offer a safe next step such as exporting the draft or clearing data the user chooses. Do not respond to a failed save by silently deleting all local data.

IndexedDB writes are asynchronous and use transactions. Listen for request and transaction errors, and treat a failed transaction as a failed save; do not show a success state just because the write was started. When cleanup is appropriate, remove only data your app can recreate, such as old versioned caches. Ask before deleting user-created records, and provide an export or server-sync path for data that matters.

If your app queues changes while offline, tell users whether a change is saved on this device or synced to the server. The offline web app guide shows a pattern for keeping local edits and server state distinct.

Request persistent storage only when it helps

The Storage API also lets an origin check whether its storage is persistent and request persistent mode:

if (navigator.storage?.persisted && navigator.storage?.persist) {
  const alreadyPersistent = await navigator.storage.persisted();
  const granted = alreadyPersistent || await navigator.storage.persist();

  if (!granted) {
    console.info("Browser storage remains best-effort");
  }
}

The browser decides whether to grant the request. Some browsers may show a permission prompt; others use engagement or installation signals. Persistent mode can reduce automatic eviction, but it does not protect data if the user clears site data, removes the browser profile, or loses the device. It is not a substitute for synchronizing or backing up important records.

Ask for persistence only when users depend on local data that cannot easily be recreated. If a request is denied, the app should still handle quota errors and explain how users can recover their work.

A practical storage plan for a small web app

  1. Keep small preferences in Web Storage. Store only simple values that are quick to recreate. Remember that localStorage is synchronous and may be unavailable under browser privacy settings.
  2. Use IndexedDB for structured offline records. Design writes so a failed transaction leaves the app in a clear, recoverable state.
  3. Use Cache API for files the app can fetch again. Version cache names and delete only your own obsolete caches; cache entries do not automatically expire just because an HTTP response has cache headers.
  4. Show a save state that matches reality. Report a local save only after it succeeds, and report a server sync only after the server confirms it.
  5. Keep a recovery path for important data. Sync it to a server, let users export it, or provide another backup. Browser storage alone is not a backup.
  6. Test failure cases. Test denied storage, quota errors, private browsing behavior, cleared site data, and an offline restart. Do not assume every device has the same quota or persistence policy.

Browser storage makes web apps faster and more resilient, but every local copy has a lifecycle. Choose the API that fits the data, handle errors at the write boundary, and make it clear what can be recovered if the browser removes the origin's data.

Official sources

Categories: Development
Related Post