Skip to content

API in Storage

StorageManager.persist() and persisted()

Published

navigator.storage.persist() asks the browser to mark the origin’s storage bucket as persistent, which exempts Cache Storage, IndexedDB, OPFS, and service worker registrations from automatic eviction under storage pressure. navigator.storage.persisted() reports whether that mark is set. Neither call changes the quota; it changes only whether the browser may clear the data without the user asking.

navigator.storage.persist()
navigator.storage.persisted()

Both return a Promise<boolean>. persist() fulfils with true when the bucket is persistent after the call, whether it was just granted or already was, and false when the browser declined. persisted() only reads the current mode. persist() is exposed on Window but not inside workers; persisted() is available in both (BCD api.StorageManager.persist: Chrome 55, Firefox 57, Safari 15.2). Both require a secure context.

None.

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: or file: document in some engines) or the user has disabled storage for the site. The promise rejects.

A refusal is not an exception. When the browser decides the site has not earned persistence, persist() fulfils with false and the origin stays best-effort.

The Storage Standard leaves the decision to the browser, and the three engines took three different routes (Storage quotas and eviction criteria, developer.mozilla.org).

Engine Behaviour on persist() What tips the decision
Chromium (Chrome, Edge) No prompt. The request is granted or silently denied, and a denied request can be made again later (Persistent storage, web.dev). Site engagement score, whether the site is installed or bookmarked, whether the notifications permission is granted.
Firefox Shows a permission doorhanger and waits for the user. The user’s click. Firefox remembers a block per site.
WebKit (Safari 15.2+) No prompt; the request is auto-granted or denied from the user’s interaction history with the site. Prior first-party interaction with the origin.

Because Chromium’s grant depends on engagement, a call on first paint of a first visit is the request most likely to return false. Calling after the user installs the app, enables notifications, or completes a meaningful action raises the odds without changing the code.

There is no API to revoke persistence from script. The user removes it through site settings (Chrome: chrome://settings/content/siteDetails?site=<origin>; Firefox: the permissions section of Page Info), or by clearing the site’s data.

The examples separate the request (once, after engagement) from the read (every launch), which mirrors how the two methods are meant to be used.

Requesting persistence after an engagement signal

Section titled “Requesting persistence after an engagement signal”

Tie the request to the moment the user does something that marks the site as valuable, and act on the boolean rather than assuming success. The example below runs after a successful install or a granted notification permission.

async function requestDurableStorage() {
if (await navigator.storage.persisted()) return true;
const granted = await navigator.storage.persist();
if (!granted) {
console.info('Storage stays best-effort; queued work will be re-synced if evicted.');
}
return granted;
}

A false result leaves the app functional: data written to IndexedDB or Cache Storage is still there until storage pressure or, in Safari, seven days of browser use without interaction (see Eviction and best-effort storage). Design the sync layer so a missing record is re-fetched or re-derived rather than treated as corruption.

Reporting the current mode in a settings screen

Section titled “Reporting the current mode in a settings screen”

persisted() is the cheap read; call it on every launch rather than caching the result, because the user can revoke persistence between visits and Chromium can grant a previously denied request once engagement rises.

async function describeStorageMode() {
const persistent = await navigator.storage.persisted();
return persistent
? 'Offline data is protected from automatic clearing.'
: 'Offline data may be cleared when the device runs low on space.';
}

Pair the sentence with the usage figures from StorageManager.estimate() so the screen explains both how much is stored and how safe it is.

navigator.storage is missing on insecure origins and in browsers older than Chrome 55, Firefox 57, and Safari 15.2. Treat a missing method as best-effort storage.

async function ensurePersistence() {
if (!('storage' in navigator) || typeof navigator.storage.persist !== 'function') {
return false; // not supported: behave as best-effort storage
}
try {
return await navigator.storage.persist();
} catch (err) {
if (err.name === 'TypeError') return false; // opaque origin or storage disabled
throw err;
}
}

The function returns the same false for “unsupported”, “declined”, and “storage disabled”, which is deliberate: the calling code has one fallback, and it is the same in each case.

Specifications

SpecificationStatus
None.