Safari and Persistent Storage: Using navigator.storage.persist() for Offline Web Apps
Learn how Safari 16+ supports the navigator.storage.persist() API, the steps to request persistent storage, how to verify it, and what limits you need to know before building offline‑first web apps.
26 Sept 2025, 18:21 UTC

Concrete Problem: Why Persistent Storage Matters in Safari
Offline‑first web apps rely on storing data locally so users can keep working even without a network. On most browsers, the default quota for background tabs is throttled to about 50 % of the available disk space. Safari 16+ introduced navigator.storage.persist() to let developers request that the browser treat an origin’s storage as “persistent” and avoid the background throttling. The question is: how do you enable it, verify it works, and what pitfalls should you watch out for?
Safari’s Implementation of navigator.storage.persist()
Safari 16 (macOS) and 16+ (iOS) expose the navigator.storage interface. The persist() method returns a Promise<boolean> that resolves to true if the request succeeded and the origin now has persistent storage. Safari enforces a soft cap of 1 GB per origin, but on iOS the quota can be higher depending on device storage. The API does not grant unlimited storage; apps must still monitor usage via navigator.storage.estimate() and handle quota‑rejection errors.
Step‑by‑Step: Requesting Persistent Storage
- Check API Availability
Run the following in the browser console (or in a script that executes on the first user interaction):
This requires no special permissions but will only run in a secure context (HTTPS).if ('storage' in navigator && 'persist' in navigator.storage) { console.log('navigator.storage.persist() is available'); } else { console.warn('Persistent storage API not supported in this browser'); } - Request Persistence
Invoke
navigator.storage.persist()and await its result. The browser may show a prompt asking the user to grant persistent storage for this origin. Subsequent calls will return the current state without re‑prompting.async function requestPersist() { try { const granted = await navigator.storage.persist(); console.log('Persistent storage granted:', granted); } catch (err) { console.error('Error requesting persistence:', err); } } requestPersist(); - Verify the Quota Change
Before and after the call, use
navigator.storage.estimate()to see thequotaandusagevalues. A typical output on Safari might look like:
If the quota is still around 512 MB after a successful persist, the request was denied or the user declined the prompt.await navigator.storage.estimate(); // { usage: 102400, quota: 1073741824 } - Persist Data and Test Across Restarts
Store a key/value pair in IndexedDB or Cache Storage, then close Safari and reopen it. Verify that the data is still present in the Storage panel of Safari’s Developer Tools. This confirms that the origin’s storage is indeed persistent.
Worked Example: Caching a JSON Payload
Below is a minimal snippet that caches a JSON object in IndexedDB and ensures it survives a Safari restart, assuming the user granted persistence.
// Open DB
const db = await indexedDB.open('offline-db', 1);
if (!db.objectStoreNames.contains('payloads')) {
db.createObjectStore('payloads');
}
// Store data
const tx = db.transaction('payloads', 'readwrite');
const store = tx.objectStore('payloads');
store.put({ id: 'demo', data: { foo: 'bar' } }, 'demo-key');
await tx.complete;
// Verify persistence
const tx2 = db.transaction('payloads', 'readonly');
const store2 = tx2.objectStore('payloads');
const result = await store2.get('demo-key');
console.log('Retrieved after store:', result);
After executing this script, close Safari, reopen it, and run navigator.storage.estimate() again. If the quota remains at 1 GB and the data is still retrievable, persistence is working.
Trade‑offs and Limitations
- One‑time Prompt: Safari will only ask the user once per origin. If the user declines, subsequent
persist()calls will simply returnfalsewithout re‑prompting, potentially leading developers to assume persistence when it isn’t available. - 1 GB Soft Cap: Even with persistence, the origin cannot exceed 1 GB. Exceeding this limit triggers a
QuotaExceededErroron write attempts. - Background Throttling Still Applies: While persistence removes the 50 % quota reduction, Safari may still throttle background activity, delaying writes if the tab is inactive.
- Data Loss on System Wipe: Persistent storage does not protect data from an iOS device wipe or a user clearing Safari data. Critical data should still be synced to a remote server.
Actionable Checklist for Developers
- Confirm
navigator.storage.persist()exists in your target Safari version. - Request persistence early in the user flow (e.g., after the first successful network fetch).
- Use
navigator.storage.estimate()to monitor quota and usage. - Gracefully handle
QuotaExceededErrorand provide fallback UI or server sync. - Test persistence across app restarts on both macOS and iOS devices.
- Document the persistence request in your app’s privacy policy, explaining that local data may be cleared if the user revokes permission.
By following these steps, you can leverage Safari’s persistent storage to deliver a robust offline experience while staying aware of its constraints.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.