Persist storage with navigator.storage.persist
Published August 20, 2027
export const ensurePersistentStorage = async (): Promise<boolean> => {
if (!navigator.storage?.persist) return false;
if (await navigator.storage.persisted()) return true;
return navigator.storage.persist();
};
export const getStorageHeadroom = async (): Promise<{ usedBytes: number; quotaBytes: number }> => {
const { usage = 0, quota = 0 } = await navigator.storage.estimate();
return { usedBytes: usage, quotaBytes: quota };
};What
navigator.storage.persist() asks the browser to mark the current origin's storage (IndexedDB, Cache API, OPFS, localStorage) as persistent, so it's exempt from the automatic eviction browsers use to reclaim disk space under pressure.
Why it matters
Without persistence, storage lives in "best-effort" mode: if the device runs low on disk space, the browser can silently delete an entire origin's storage — usually the least-recently-used one — with no warning to the user and no event your app can catch beforehand. For an app that keeps meaningful state client-side (an offline-first note-taking app, a local-first editor, cached media for offline playback), that's data loss the user never agreed to. persist() moves an origin out of that eviction pool; once granted, the browser will only clear it if the user does so explicitly (clearing site data, uninstalling the app).
How it works
ensurePersistentStoragefirst checksnavigator.storage?.persistexists — Web Workers don't exposepersist()(onlypersisted()), so this doubles as an environment check.- It checks
persisted()before callingpersist()— asking again when storage is already persistent is harmless but wasteful, and on some browsers repeated calls can re-trigger evaluation logic. getStorageHeadroomcallsestimate(), which returnsusageandquotain bytes; both are optional in the type because a handful of edge cases (opaque origins, storage disabled) omit them, hence the destructuring defaults.
Gotchas
- Chrome auto-decides, silently. It grants or denies
persist()based on a site engagement heuristic — visit frequency, whether the site is installed or bookmarked, notification permission — with no prompt shown to the user either way. A fresh, rarely-visited origin will likely getfalseback on the first call. - Firefox asks the user directly, via a permission popup, the first time a site calls
persist(). - Safari has supported
persist()/persisted()since Safari 15.2, but doesn't publicly document its granting heuristic the way Chrome does — treat it as opaque and test the actual return value rather than assuming. - A
falsereturn isn't an error — it means "keep working, but your storage can still be evicted." Design for that outcome (e.g. periodic export, sync-to-server) rather than treating persistence as guaranteed. estimate().quotais a ceiling shared across all storage APIs for the origin combined (IndexedDB + Cache API + OPFS, etc.), not a separate budget per API.
Related
- Cache API / IndexedDB — the storage this actually protects;
persist()changes eviction behavior, it doesn't create or manage storage itself. - Storage Buckets API — a newer, more granular alternative that lets you set persistence and eviction policy per named bucket instead of for the whole origin at once.