Swoff Swoff

iOS Safari Limitations

Understand iOS Safari's PWA constraints — storage limits, eviction, service worker lifecycle, and workarounds.

Safari on iOS supports Service Workers, Cache API, and IndexedDB — but with significant constraints that differ from desktop browsers. Understanding these is essential for production offline-first apps.

7-Day Eviction Policy

The most critical limitation: iOS Safari evicts all website data — IndexedDB, Cache API, and Service Worker registrations — after 7 days of no user engagement.

ScenarioData Persistence
User visits weeklyData survives (engagement resets 7-day timer)
User installs to Home ScreenData persists indefinitely
User visits once, never returnsData evicted after 7 days
User visits via installed PWAData persists (standalone exception)
User clears Safari dataImmediate eviction (user-initiated)

Mitigation

Encourage PWA installation — standalone mode is the only reliable protection. Detect eviction on startup:

async function detectEviction() {
  try {
    const db = await openDB();
    const tx = db.transaction("meta", "readonly");
    const store = tx.objectStore("meta");
    const marker = await new Promise((r) => {
      const req = store.get("app-initialized");
      req.onsuccess = () => r(req.result);
    });
    return !marker;
  } catch {
    return true;
  }
}

IndexedDB Storage Limit

iOS Safari imposes a ~50 MB soft limit on IndexedDB — significantly lower than desktop browsers (typically unlimited up to free disk space). Above 50 MB, QuotaExceededError is thrown on writes.

Monitoring

async function monitorStorage() {
  const estimate = await navigator.storage.estimate();
  const usageMB = estimate.usage / (1024 * 1024);
  const quotaMB = estimate.quota / (1024 * 1024);
  if (usageMB > 40) console.warn("Approaching iOS IndexedDB limit");
  return { usageMB, quotaMB };
}

Note: navigator.storage.estimate() on iOS often reports a very large quota (~500 MB+) but writes will still fail around 50 MB. Trim cached data aggressively and implement proactive cleanup.

No beforeinstallprompt

iOS Safari does not fire the beforeinstallprompt event. Use the Share menu "Add to Home Screen" flow instead:

function showInstallGuide() {
  if (window.navigator.standalone) return;
  const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
  if (isIOS && "serviceWorker" in navigator) {
    showIOSInstallInstructions(); // 1. Share → 2. Add to Home Screen
  }
}

Detect Standalone Mode

const standalone = {
  ios: window.navigator.standalone,
  standard: window.matchMedia("(display-mode: standalone)").matches,
};

WKWebView Constraints

Third-party browsers on iOS (Chrome, Firefox, Edge) are WKWebView wrappers around Safari's engine. They share Safari's limitations:

ConstraintDetail
Service Worker supportAvailable from iOS 16.4+ in WKWebView. Before 16.4, no SW support.
Storage sharingEach browser has its own storage sandbox
IndexedDB limitsSame 50 MB soft limit
Eviction policySame 7-day eviction (no standalone exception for most)

Push Notifications

iOS VersionPush Support
< 16.4Not supported
16.4+Supported (requires standalone mode + APNs entitlement)

Push notifications on iOS can only be tested on a physical device, not the simulator.

Private Browsing Mode

In Safari private browsing, IndexedDB and Cache API may throw errors or return empty databases. Data does not persist after the session ends.

async function isPrivateMode() {
  try {
    const db = indexedDB.open("__detect_private__");
    return new Promise((resolve) => {
      db.onerror = () => resolve(true);
      db.onsuccess = () => {
        indexedDB.deleteDatabase("__detect_private__");
        resolve(false);
      };
    });
  } catch {
    return true;
  }
}

Service Worker Lifecycle on iOS

iOS terminates service workers more aggressively than desktop:

BehaviorDesktop (Chrome)iOS Safari
SW idle timeout~30 seconds~5-10 seconds
SW wakes on fetchYesYes
SW wakes on pushYesYes (iOS 16.4+)
SW wakes on sync eventYesN/A (no Background Sync)
Extension via waitUntilFull operation completesMay be interrupted earlier

Implications: Background sync does not exist on iOS — use the online event listener instead. Mutation queue processing must happen in the main thread, not the SW.

QueryBehavior
navigator.storage.persist()Returns false (denied)
navigator.storage.persisted()Returns false
Standalone PWA exceptionData is not evicted even without persistent storage

On iOS, persistent storage requests are always denied. The only reliable prevention is standalone mode (Home Screen installation).

Compatibility Summary

FeatureDesktop ChromeiOS SafariiOS Standalone PWA
Service WorkerFullSupportedSupported
Cache APIFullSupportedSupported
IndexedDBUnlimited~50 MB limit~50 MB limit
7-Day EvictionNoYesNo (exempt)
Background SyncSupportedNot supportedNot supported
Push NotificationsFulliOS 16.4+ onlyiOS 16.4+
beforeinstallpromptSupportedNot supportedN/A
Persistent StorageSupportedDeniedN/A
Private Browsing IDBWorksMay failN/A
SW Idle Timeout~30s~5-10s~5-10s

Testing on iOS

MethodLimitation
Safari on real deviceBest option — reflects actual behavior
iOS Simulator (Xcode)Safari works, but no push, no standalone mode simulation
Remote debuggingSafari → Develop → [Device Name] → Web Inspector
BrowserStack / Sauce LabsGood for quick checks, limited SW debugging

Next Steps

On this page