Platform in Storage
Home Screen web app storage isolation on iOS
Published Updated
A site configured as a Home Screen web application — the manifest and metadata requirements are in “Platform scope” below — opens as its own app rather than in a browser, and WebKit documents that the website data of such an app is kept isolated from Safari. A visitor who signs in inside Safari and then taps that icon therefore arrives at the same origin with a separate container, which the browser’s session did not write to. Before iOS 26, adding a site that carries no such configuration produced a Home Screen bookmark instead, which from iOS and iPadOS 16.4 opens in the default browser and so is not the case this page describes. On iOS 26 and iPadOS 26 every added site opens as a web app by default, configured or not, unless the user turns off “Open as Web App” while adding it.
How it works
Section titled “How it works”Two WebKit documents state the rule and its rationale, and a third sets out what has to be true before a saved site behaves as an app at all.
What WebKit documents about the isolation
Section titled “What WebKit documents about the isolation”The tracking-prevention page carries a section headed “Home Screen Web Application Domain Exempt From ITP”. It states that “the first-party domain of home screen web applications is exempt from ITP’s 7-day cap on all script-writeable storage, i.e. ITP always skips that domain in its website data removal algorithm”, and adds that “the website data of home screen web applications is kept isolated from Safari and thus will not be affected by ITP’s classification of tracking behavior in Safari” (Tracking Prevention in WebKit, webkit.org).
The separation is deliberate rather than a side effect. On WebKit bug 181849, “Add to homescreen” apps don’t share storage with Safari, Brent Fulgham replied on 2022-02-01: “The current behavior (on Apple platforms) is by design. Home Screen apps are created as isolated entities without shared state with the browser” (Bug 181849, bugs.webkit.org).
So the isolation gives the installed app two things at once: script-writeable storage that the 7-day cap skips, and a container outside the reach of Safari’s own classification outcomes. What it costs is any assumption of continuity with the browser session the visitor started in.
Platform scope
Section titled “Platform scope”The rule applies to a Home Screen web app, which WebKit defines by its manifest: serving a manifest file whose display member is set to standalone or fullscreen alongside the site makes it a Home Screen web app, and tapping its icon then opens the web app like any other app on iOS or iPadOS instead of opening it in a browser. Its app preview appears in the App Switcher, separate from Safari or any other browser (Web Push for Web Apps on iOS and iPadOS, webkit.org).
A saved site without that configuration is a different object. WebKit states that if there is no manifest file configured to request web app behaviour, and no meta tag marking the site as web app capable, the website is saved as a Home Screen bookmark, and that starting in iOS and iPadOS 16.4 Home Screen bookmarks open in the user’s current default browser. The same post records that iOS has supported multiple installs of the same web app since the very beginning, and that iOS and iPadOS 16.4 combine the name the user chooses when adding the app with the Manifest ID to uniquely identify it.
iOS 26 and iPadOS 26 take that decision away from the site. WebKit’s Safari 26.0 release notes state that “by default, every website added to the Home Screen opens as a web app”, and that a user who prefers a browser bookmark can disable “Open as Web App” when adding to Home Screen, “even if the site is configured to be a web app”. The same notes say the change removes none of WebKit’s web app support (a manifest’s icons are still used) and that “there are now zero requirements for ‘installability’ in Safari” (WebKit Features in Safari 26.0, webkit.org). For this page the consequence is a change of default: on those releases a site with no manifest and no meta tag also launches as a web app unless the user switched the toggle off, so the manifest and meta-tag test above decides the outcome on releases before 26, and on 26 the user’s toggle decides it. PWAs on iOS by version places the change among the other releases.
What this costs the user
Section titled “What this costs the user”The bug report that produced Apple’s answer describes the consequence in the reporter’s own terms: a PWA that “can save entire user projects in to IndexedDB” leaves users of the web app at risk of losing their work, and “it’s also inconvenient since details like saved logins are not remembered between the two”. The same thread records the workaround developers reached for: on 2018-02-27 Thomas Steiner described carrying session data from Safari over to the web app with window.location.query, which he called “the only working hack (that I am aware of)”.
Examples
Section titled “Examples”The first example tells the two surfaces apart from script; the second reports what the current container actually holds, which is what makes the difference visible in a support session.
Detecting that the page is running as an installed app
Section titled “Detecting that the page is running as an installed app”The display-mode media feature tests whether a web app is being displayed in a normal browser tab or in some alternative way, such as a standalone app or fullscreen mode, and a PWA that sets display in its manifest sees that value reflected, though MDN notes the reported value may differ from the requested one when the browser does not support the requested mode (display-mode, developer.mozilla.org). The same page adds that “any web app can use the Fullscreen API or the Document Picture-in-Picture API to set the display mode, and in this case the value of display-mode identifies the mode that was set”, and documents the fullscreen keyword as applying “when the app has been put in fullscreen mode by the Fullscreen API or by using the fullscreen value of the display member of the Wep App Manifest”. An ordinary browser tab that called Element.requestFullscreen() therefore matches (display-mode: fullscreen) too, so that value alone does not establish that the page is the isolated Home Screen container. The signal specific to this platform is Apple’s own: “You can determine whether a webpage is displaying in standalone mode using the window.navigator.standalone read-only Boolean JavaScript property” (Configuring Web Applications, developer.apple.com).
function launchSurface() { // Apple documents this as a Boolean that reports whether the page is in standalone mode. if (navigator.standalone === true) return 'installed'; if (!('matchMedia' in window)) { return 'unknown'; // No media-query interface: treat the session as a browser tab. } if (window.matchMedia('(display-mode: standalone)').matches) return 'installed'; // The Fullscreen API matches this too, so it cannot stand in for an install signal. if (window.matchMedia('(display-mode: fullscreen)').matches) return 'ambiguous'; return 'browser';}The unknown branch returns a value the caller can route on rather than throwing, so the onboarding path stays the browser path wherever the feature is unavailable to query. Only installed is the cue to offer an in-app sign-in instead of assuming a session carried over; ambiguous belongs on the browser path too, unless installation has been established some other way, because a tab the page itself pushed into fullscreen reaches that branch.
Reporting what the current container holds
Section titled “Reporting what the current container holds”Running the same function in Safari and in the installed app produces two readings a support engineer can compare. CacheStorage.keys() resolves with the names of every Cache the container tracks, in creation order (CacheStorage: keys() method, developer.mozilla.org), and window.localStorage gives access to a Storage object for the document’s origin whose stored data is saved across browser sessions (Window: localStorage property, developer.mozilla.org).
async function storageFingerprint() { const cacheNames = 'caches' in window ? await caches.keys() : []; let localKeys = []; try { localKeys = Object.keys(localStorage); } catch { localKeys = []; // Storage unavailable in this context: report an empty set. } return { surface: launchSurface(), cacheNames, localKeys };}Compare the returned surface, cacheNames and localKeys values from the two containers. Different key lists alone do not establish that isolation caused the difference or rule out an application write failure.
See also
Section titled “See also”- Tracking Prevention in WebKit (webkit.org)
- Web Push for Web Apps on iOS and iPadOS (webkit.org)
- Bug 181849: “Add to homescreen” apps don’t share storage with Safari (bugs.webkit.org)
- WebKit Features in Safari 26.0 (webkit.org)
- PWAs on iOS by version (iOS 11.3 to iOS 26)
- localStorage and sessionStorage
- CacheStorage and the caches global
- Eviction and best-effort storage
Specifications
| Specification | Status |
|---|---|
| None. | |