Skip to content

Installation by Platform

Installing a Progressive Web App produces a different artifact on every operating system and browser: a signed Android package minted by a server, a Windows Start menu entry that launches chrome_proxy.exe, a macOS app bundle, a freedesktop .desktop file, a WebKit Home Screen app with its own storage container, or a browser-badged shortcut. Those artifacts decide where the icon appears, whether links open in the app, how manifest changes reach users, where the app's cookies and IndexedDB live, and whether uninstalling deletes anything. This page walks through each platform in September 2026: the exact user flow, what gets written to the system, how updates and uninstalls behave, and how to inspect the result when something goes wrong.

Key takeaways

  • Chrome on Android with Google Play services mints a WebAPK, a real APK generated by Google's server from your manifest and installed silently through Play services. Samsung Internet does the same on Samsung devices. Firefox, Edge, Opera and Chrome without Play services create browser-badged shortcuts instead.
  • Chromium on desktop writes OS-native launchers: Start menu shortcuts and an Installed apps entry on Windows, app shim bundles in ~/Applications/Chrome Apps.localized/ on macOS, .desktop files on Linux, and system-managed apps on ChromeOS. The app still runs in the browser profile it was installed from and shares that profile's storage.
  • Safari creates isolated apps. An iOS or iPadOS Home Screen web app, and a macOS Dock web app, gets its own website data store. Safari copies the site's cookies into it once, at creation, and nothing else.
  • Chrome desktop captures links into installed apps: on Windows, macOS and Linux, in-scope links that would open a new tab or window now open the installed app unless the user opts out (Chrome 138 for apps with launch_handler.client_mode, Chrome 140 for all apps). Safari 18 on macOS opens in-scope links from other apps in the Dock app.
  • Firefox desktop web apps are on by default only on Windows (Firefox 143, and from Firefox 150 in the Microsoft Store build), as taskbar-pinned windows; Linux has them behind a preference and macOS not at all. Firefox for Android creates badged shortcuts. There's no install event anywhere in Firefox.
  • Organizations can install apps for users with the WebAppInstallForceList policy in Chrome and Edge; users can't uninstall those apps.
  • Uninstalling rarely deletes data by default. Chromium keeps the site's storage unless the user ticks the delete-data checkbox; a removed WebAPK leaves the origin's data in Chrome; only Safari's per-app containers disappear with the app. No uninstall event reaches your code.

The install matrix at a glance

The table compares what each browser and platform combination actually produces. The sections that follow explain each row in detail. Installing Progressive Web Apps covers the concepts that apply everywhere, and Installability Criteria covers what each browser checks before it offers or promotes installation.

Platform and browser User entry point What the OS gets Standalone window Storage Removed from
Android, Chrome (with Play services) Install message, or More menu, Install WebAPK: signed APK, package org.chromium.webapk.* Chrome's WebAPK activity Chrome's profile, shared with tabs Settings > Apps, launcher
Android, Chrome (no Play services) More menu Browser-badged launcher shortcut Opens in Chrome Chrome's profile Home screen
Android, Samsung Internet (Samsung device) URL bar install icon, menu WebAPK Samsung Internet Samsung Internet's profile Settings > Apps
Android, Firefox, Edge, Opera, Brave Menu Browser-badged shortcut Browser's web-app activity or tab That browser's profile Home screen
Windows, Chrome or Edge Address bar icon, menu Start menu shortcut, optional desktop and taskbar pins, Installed apps entry Chromium app window Browser profile Installed apps, app menu, chrome://apps / edge://apps
Windows, Firefox 143+ Firefox UI, pin to taskbar Taskbar-pinned web app Simplified Firefox window Firefox profile Taskbar, Firefox
macOS, Chrome or Edge Address bar icon, menu App shim .app bundle Chromium app window Browser profile Finder, Launchpad, app menu
macOS, Safari 17+ File > Add to Dock, Share .app in ~/Applications Safari web app window Own container, cookies copied once Drag to Trash
Linux, Chrome or Edge Address bar icon, menu .desktop file and icons Chromium app window Browser profile App launcher, app menu
ChromeOS, Chrome Address bar icon, menu System launcher and shelf app Chromium app window Browser profile Launcher, Settings > Apps
iOS and iPadOS, any browser (16.4+) Share, Add to Home Screen Home Screen web app WebKit web app Own container, cookies copied once (17.2+) Home Screen long-press

Support data as of September 2026. See MDN's installation guide and caniuse for live data.

Chrome for Android

Chrome for Android has the most elaborate install pipeline of any browser, because it turns your website into a real Android package.

Install versus a home screen shortcut

Chrome offers two outcomes from its menu, and users routinely confuse them:

  • Install produces a WebAPK. The app appears in the app drawer, in Settings > Apps, in the recent-apps switcher with your name and icon, and it registers intent filters so links into your scope open in the app.
  • Create shortcut (or Add to home screen on sites Chrome can't install) pins a launcher shortcut. It opens the page in Chrome.

The menu wording has changed repeatedly. Google's current Chrome Help article on web apps describes the Android flow as More (⋮), then Install and create shortcut, then Install. Older releases used Add to home screen, which became Install app on installable sites, and in 2024 Chrome tested a unified sheet that offers both Install and Create shortcut for every site. When you write install instructions for Android users, describe the outcome ("choose Install, not the shortcut option") rather than depending on an exact label, and prefer your own button driven by beforeinstallprompt, as described in Install Prompts & Custom UI.

Chrome also promotes installable sites itself with an install message or bottom sheet. That promotion is rate-limited by Chrome's own heuristics, while the beforeinstallprompt event you capture isn't.

How a WebAPK is minted

A WebAPK is generated on Google's servers, not on the device. The flow:

sequenceDiagram
    participant User
    participant Chrome
    participant Server as WebAPK minting server
    participant Play as Google Play services
    participant Launcher as Android launcher
    User->>Chrome: Accepts the install dialog
    Chrome-->>Chrome: Fires appinstalled on the page
    Chrome->>Chrome: Collects manifest data, icons, shortcuts, share target
    Chrome->>Server: Requests a WebAPK for this manifest
    Server-->>Chrome: Signed APK built from the WebAPK shell
    Chrome->>Play: Hands over the APK for installation
    Play->>Launcher: Installs package org.chromium.webapk.*
    Launcher-->>User: Icon appears in the app drawer and home screen

Points that matter to developers:

  • appinstalled fires early. web.dev's detection chapter notes that on Android with WebAPK the event fires when the user accepts the dialog, "not after the WebAPK is minted and installed". The icon can appear several seconds later, and on a slow network, much later. Don't tell the user "look for the icon on your home screen" in the same instant.
  • The package is signed by Google, not you. You can't sign, modify, publish or distribute a WebAPK. web.dev states that WebAPKs "are not listed in the Play Store" and can't be uploaded to it. If you need a Play listing, build a Trusted Web Activity and see Publishing to App Stores.
  • The APK is a thin shell. Chromium's WebAPK README describes a runtime library "stored in the Chrome APK's assets and which is extracted from the Chrome APK by the WebAPK at runtime", so most of the launcher logic updates with Chrome, not with the WebAPK. The site runs in Chrome itself, not in a WebView. web.dev: "the site opens in the version of Chrome the user added the site from."
  • Minting has constraints beyond the promotion criteria. Manifest URLs with embedded credentials fail with url-not-supported-for-webapk, and if the minting service is unreachable or refuses, Chrome falls back to a shortcut. Installability Criteria lists the error IDs.
  • Only devices with Google Play services get WebAPKs from Chrome. On devices without them, MDN notes that Chrome instead adds "a browser-badged home-screen shortcut that opens the site in the browser".

What the generated Android package contains

The WebAPK's AndroidManifest.xml bakes in a snapshot of your web manifest:

Web manifest member What the WebAPK does with it
name, short_name App label in the drawer and launcher
icons (prefers maskable) Adaptive launcher icon and splash icon
start_url The URL launched from the icon
scope An intent filter with scheme, host and android:pathPrefix, so links into the scope open in the app
display, orientation Activity configuration
theme_color, background_color Status bar and splash screen colors
shortcuts Android launcher shortcuts on long-press (see App Shortcuts)
share_target An ACTION_SEND intent filter so the app appears in the share sheet (see Web Share Target)

Because the scope becomes an intent filter, link capturing on Android is an operating system feature. A link to https://example.com/app/orders/42 tapped in a chat app opens the WebAPK if scope is /app/, before any browser gets involved. web.dev summarizes: "The scope property tells Android to only open your web app if the URL matches the origin + scope." Keep the scope as narrow as your app really is. A scope of / on a domain that also hosts a blog and a help center sends every one of those links into the app.

Notification permission is not granted at install

Native Android apps get notification permission handling from the OS, but web.dev is explicit that WebAPKs are "not granted this at install time; you must request it at runtime." Installation doesn't change the permission state of the origin. Ask with Notification.requestPermission() from a user gesture, as in a tab; see Push Notifications.

How WebAPK updates reach users

A WebAPK updates only when Chrome notices a manifest change while the WebAPK is launched, at most once a day (Chrome 76 moved the interval from three days to one), and then only by minting and installing a new APK. Chrome's documentation lists the members that trigger an update: name, short_name, icons, background_color, display, orientation, scope, shortcuts, start_url, theme_color and web_share_target. The new APK is requested after all windows of the app have closed and, per web.dev, when "the device is plugged in, and connected to WiFi". A changed name shows the user a confirmation dialog. Chrome Help warns users that "drastic" name changes resembling a different app could indicate malicious activity.

The practical consequences: users who never launch the installed app never get updates, users on mobile data get them late, and scope or shortcut changes take days. Server-side redirects are the only instant tool. App Identity & Updates documents the update pipeline, the dialogs and the icon thresholds in full.

Uninstalling a WebAPK and what happens to data

Users remove a WebAPK like any Android app: long-press the icon and choose Uninstall, or go to Settings > Apps, pick the app and tap Uninstall. This removes the package. It doesn't remove your origin's data, because that data never lived in the package. web.dev: "Chrome uses the current profile to store any data, and it will not be segregated away." Cookies, IndexedDB, Cache Storage and the service worker registration remain in Chrome, and a user who opens your site in a Chrome tab afterward is still signed in.

The reverse also holds: "if the user clears their Chrome profile, or chooses to delete site data, that will apply to the WebAPK as well." Clearing Chrome's storage from Settings > Apps > Chrome > Storage signs the user out of every installed web app at once.

Inspecting WebAPKs on a device

Chrome's internal page about://webapks lists every WebAPK installed through that Chrome channel, with the package name, the manifest URL, the start_url, scope, display mode, the last update check, and an Update button that forces a check on next launch. From a computer, adb shows the packages themselves:

inspect-webapks.sh
#!/usr/bin/env bash
# Lists WebAPKs on a connected Android device and prints the intent filters
# Chrome generated from each web manifest's scope.
set -euo pipefail

# Every Chrome-minted WebAPK uses this package prefix.
packages=$(adb shell pm list packages | tr -d '\r' | sed 's/^package://' | grep '^org\.chromium\.webapk\.' || true)

if [[ -z "$packages" ]]; then
  echo "No WebAPKs found. Shortcuts created by other browsers are not packages." >&2
  exit 1
fi

for pkg in $packages; do
  echo "== $pkg"
  # versionCode increases each time Chrome installs an updated WebAPK.
  dump=$(adb shell dumpsys package "$pkg" | tr -d '\r')
  # grep exits 1 when nothing matches; don't let set -e abort the loop.
  grep -E 'versionCode|lastUpdateTime' <<<"$dump" | head -n 2 || true
  # The http/https filters mirror your manifest scope.
  grep -E 'Scheme: "https?"|Authority: |Path: ' <<<"$dump" | sort -u \
    || echo "   (no web intent filters: check the manifest scope)"
done

If your app isn't listed at all, installation produced a shortcut: the device lacks Play services, the minting request failed, or the user chose the shortcut option.

Samsung Internet

Samsung Internet is Chromium-based and has its own install UI. When it detects an installable PWA, it shows an install icon in the URL bar; the same action is in its menu. On Samsung devices, it installs a WebAPK, which MDN describes as giving the app "a real entry in the app launcher and switcher, and in the system settings". On non-Samsung Android devices it creates a shortcut.

For developers, Samsung Internet behaves like Chrome in three ways that matter: it uses Blink's manifest parser, so id, start_url and scope resolve identically; it supports beforeinstallprompt (since version 5.0 per MDN's data) and appinstalled (since 7.0); and its WebAPKs run in Samsung Internet's own profile. The last point has a consequence users notice: a user who signs in to your site in Chrome and installs the app from Samsung Internet isn't signed in inside the app.

Two more differences are worth knowing. Samsung Internet mints its WebAPKs through its own service, and the update cadence and about://webapks diagnostics described below are Chrome's; Samsung doesn't publicly document its update pipeline, so don't assume manifest changes reach Samsung installs on Chrome's schedule, and verify on a device. And a site installed in both browsers produces two separate apps with two separate storage areas on the same phone.

Test on a real Samsung device. Minting, the install UI and failure modes differ from Chrome's, and an emulator image without Samsung's services can't reproduce them.

Firefox for Android

Firefox for Android supports manifest-based installation, but the result is a pinned launcher shortcut badged with the Firefox logo, not a WebAPK. The manifest must have name or short_name, a display other than browser (Gecko treats a missing display as browser), and an icon of at least 192 px; the criteria are detailed in Installability Criteria. When they pass, the menu offers Add app to Home screen or Install (the label depends on the version) instead of a plain shortcut.

The shortcut launches Firefox's standalone web-app activity, and the recent-apps switcher shows your name, icon and theme_color. What it doesn't get: an app drawer entry, an entry in Settings > Apps, OS-level link capturing, launcher shortcuts or share target registration. Firefox doesn't implement beforeinstallprompt or appinstalled, so your UI can only show instructions. Removing the icon from the home screen is the uninstall. Storage stays in the Firefox profile.

Microsoft Edge, Opera, Brave and other browsers on Android

Only browsers that have a minting server trusted by the device can create WebAPKs. Chrome uses Google's; Samsung Internet uses Samsung's on Samsung hardware. Every other Android browser, including Edge, Opera and Brave, creates a browser-badged shortcut. MDN's installation guide groups them explicitly: "Firefox, Edge, Opera, and other browsers including Chrome on devices where GMS is not present, instead add a browser-badged home-screen shortcut that opens the site in the browser." Microsoft hasn't documented WebAPK support for Edge on Android as of September 2026.

If your Android audience matters and uses these browsers heavily, the only way to give them a real app entry with link capturing is a store-distributed Trusted Web Activity, which delegates to the user's default browser if it supports TWAs.

Chrome and Edge on desktop

Chromium's desktop web app system is shared by Chrome, Edge and other Chromium browsers on Windows, macOS, Linux and ChromeOS. The pipeline is the same; the OS integration layer differs per platform.

Starting an install

Browser Promoted install (installable sites) Menu path (any site) Management page
Chrome Install icon in the address bar More (⋮) > Cast, save, and share > Install page as app… chrome://apps
Edge App available icon in the address bar Settings and more > Apps > Install this site as an app (Apps sits under More tools in recent versions) edge://apps

Since 2024, Chrome's Install page as app works for pages that don't meet the installability criteria: Chrome builds an app from the page's title, favicon and a default standalone display. Chrome 128 changed Create shortcut to produce a shortcut that opens in a normal tab, leaving Install page as app as the only path to a standalone window. Installing from your manifest is still far better: an app installed without it gets no shortcuts, handlers or share target, and its id defaults to whatever URL the user happened to be on.

After the install, Edge's post-install dialog on Windows offers Pin to Start, Pin to taskbar and Auto-start on device login, and the installed app's Settings and more menu keeps the pinning commands.

What Chromium creates on Windows

On Windows, installed Chromium apps integrate like native programs: Microsoft's documentation lists the taskbar (where they can be pinned), the Start menu, Alt+Tab, and Settings > Apps > Installed apps, where they appear with an Uninstall action. Chromium's Windows PWA integration notes describe the moving parts:

  • The launcher shortcut runs chrome_proxy.exe (or Edge's equivalent) with the app ID and the profile, and chrome_proxy.exe relaunches the browser with the same command line, which opens the app window in a running browser process or starts one. Chromium's notes explain the indirection: the shortcut targets the proxy instead of chrome.exe to work around a Windows 10 Start menu pinning bug. The Start menu entry goes into a per-browser apps folder such as Chrome Apps.
  • Per-app files live in the profile directory under Web Applications/<app_id>: icons, shortcut icons and, when the app declares file handlers, a per-app launcher named after the app. That launcher is a hard link to the browser's canonical chrome_pwa_launcher.exe (a copy when the profile is on a different drive). It finds the browser through the Last Browser file in the user data directory, and the browser refreshes every launcher after it updates itself.
  • File handlers are registered under HKCU\Software\Classes\<ProgID>, where the ProgID is <BaseAppId>.<hash(profile + app ID)> (hashed because ProgIDs are limited to 32 characters). The registered command is <launcher> --app-id=<app_id> --profile-directory=<profile_dir>, and a File Extensions value records which extensions to unregister when the app is uninstalled or its file_handlers change.
  • Multiple profiles can install the same app. Chromium disambiguates by appending the profile name in parentheses, such as "Example PWA (profile1)".
  • Protocol handlers declared with the manifest's protocol_handlers are registered with the OS in the same way. See Protocol Handlers & Launch Handling.

Everything is registered per user (HKCU) and per browser profile. An install doesn't need administrator rights, and it isn't visible to other Windows accounts. Microsoft's documentation also notes a limitation for virtual desktop deployments: PWAs "aren't supported in FSLogix environments"; an install disappears after sign-out and sign-in.

What Chromium creates on macOS

On macOS, Chromium creates an app shim: a small .app bundle that the Dock, Launchpad, Spotlight and Cmd+Tab treat as a separate application. Chrome places shims in ~/Applications/Chrome Apps.localized/; other Chromium browsers use their own folder. When the user launches it, the shim connects to the running browser over Mojo IPC (starting it if needed), and then either exits or acts as the host for the app's windows so that they appear under the app's own Dock icon and menu bar. The code lives in Chromium's chrome/app_shim directory.

Two consequences follow. First, the shim has no copy of your site; deleting the browser breaks every shim. Second, deleting a shim in the Finder doesn't uninstall the app from Chrome's perspective: chrome://apps still lists it, with its data and registrations. Uninstall from the app window's menu or from chrome://apps instead.

What Chromium creates on Linux

On Linux, installing writes a freedesktop.org .desktop entry, named after the browser, app ID and profile (for example chrome-<app_id>-Default.desktop), to the user's applications directory, usually ~/.local/share/applications/, plus icons under ~/.local/share/icons/. Desktop environments that follow the freedesktop specifications pick these up in their launchers. Sandboxed browser packages such as Flatpak or Snap may lack permission to write to those directories, in which case the install appears to succeed but no launcher entry appears; the fix is a filesystem permission for the package, not anything in your web app.

ChromeOS

On ChromeOS, installed web apps are first-class system apps. They appear in the launcher and can be pinned to the shelf, and users manage them in Settings > Apps. To uninstall, the user right-clicks the icon in the launcher and chooses Uninstall; Chrome Help notes the dialog's Also delete browsing data option "removes all website-related data". ChromeOS also runs Android apps, so a Trusted Web Activity from Google Play is a second way the same PWA can arrive on a Chromebook. Bubblewrap even offers a ChromeOS-only build option for that case.

ChromeOS had link capturing as a per-app user preference before the other desktop platforms: in an installed app's settings, users choose whether supported in-scope links open in the browser or the app. The unified capturing described next is available on ChromeOS too, but it is off by default there for each app until the user turns it on.

Desktop Chrome used to open links in a tab and show an icon in the address bar indicating that an installed app could handle the page. The change rolled out in stages from Chrome 134 and is now on by default on Windows, macOS and Linux: from Chrome 138 for apps that declare launch_handler.client_mode, and from Chrome 140 for all apps. Chrome's documentation on navigation management describes it: "The new, unified approach for navigation capturing, automatically opens links in their corresponding installed PWA." A navigation is capturable "if it creates a new frame and does not open in an auxiliary browsing context", which in practice means a user clicking a link that opens a new tab or window. Links "will only fall back to a browser tab if the PWA isn't installed or if the user has opted out" in the app's settings.

Once Chrome decides to launch the app, the manifest's launch_handler decides which window handles it: client_mode of focus-existing, navigate-existing or navigate-new. Protocol Handlers & Launch Handling shows how to receive those launches with launchQueue.

The app window and its menu

The Chromium app window has no tab strip or address bar in standalone mode. Its menu (the three-dot button in the title bar) provides App info (the origin and site settings), Copy URL, Open in Chrome or Open in Edge, zoom, find, print, cast, and Uninstall. Navigations out of scope show a bar with the page's URL and title, so the user always sees which site they're on; see Display Modes for how minimal-ui and Window Controls Overlay change the frame.

Manifest updates on desktop

Current desktop Chromium checks for a manifest update whenever a page that links your manifest, with a matching id, becomes the primary page of a tab or app window. It applies non-security fields silently and asks the user before applying a name change or a substantially different icon. Icons are compared by their manifest entries, so replacing the bytes behind an unchanged icon URL does nothing. The mechanics, the version history and the testing switches are in App Identity & Updates.

Uninstalling on desktop and what happens to data

Where Path Data option
Chrome, Windows, macOS or Linux App window More > Uninstall [app name] > Remove Opt-in checkbox Also delete data from Chrome
Chrome, ChromeOS Right-click the launcher icon > Uninstall Opt-in Also delete browsing data
Chrome, any desktop chrome://apps, right-click the app > Remove from Chrome Same dialog
Edge App menu, edge://apps > Details, or Settings and more > More tools > Apps > View apps > Manage app Same Chromium dialog
Windows Settings > Apps > Installed apps (or Add or remove programs) > Uninstall Uninstall only

By default, the site's storage survives uninstalling, because it belongs to the browser profile. Only the checkbox deletes it, and Chrome Help is explicit about what that means: "If you delete browsing data, all website-related data is also removed in Chrome. The next time you visit the website, you'll need to sign in again." The deletion covers the whole origin, including data your site created in normal tabs.

Safari on iOS and iPadOS

On iPhone and iPad, installation is always user-initiated through the Share sheet. There's no install event, no promotion and no API.

The Add to Home Screen flow

Apple's iPhone User Guide describes the current flow: open the site, tap ⋯ and then Share (or tap Share directly with the Bottom or Top tab layout), choose Add to Home Screen (tap Edit Actions to add it if it's missing), keep Open as Web App switched on, and tap Add. The sheet shows the icon, an editable name pre-filled from your metadata, and the Open as Web App toggle. iOS doesn't launch the app after adding it.

Since iOS and iPadOS 26, every site opens as a web app by default. WebKit's Safari 26 announcement puts it plainly: "There are now zero requirements for 'installability' in Safari." On iOS 18 and earlier, a site without a manifest display of standalone or fullscreen (or the legacy apple-mobile-web-app-capable meta tag) became a bookmark that opens a Safari tab. iOS & iPadOS covers the flow, the metadata Safari reads and the full timeline.

Other browsers since iOS 16.4

Since iOS and iPadOS 16.4, browsers other than Safari can add web apps to the Home Screen through their own share menus. MDN lists Safari, Chrome, Edge, Firefox and Orion. All of them use WebKit, and the web app they create is the same WebKit app Safari creates: it doesn't open in the browser that added it, and it doesn't carry that browser's extensions or settings. Apple documents cookies being copied from Safari at creation; whether cookies are copied when Chrome, Edge or Firefox creates the app isn't documented, so don't rely on it. In Chrome for iOS, Add to Home Screen is in the share menu opened from the address bar's share button.

What iOS creates

A Home Screen web app is a separate app with:

  • Its own website data store. Cookies, localStorage, IndexedDB, Cache Storage and service worker registrations are separate from Safari and from every other web app. Since iOS 17.2, iOS copies Safari's cookies for the site into the new app when it's created, so cookie-based sessions survive installation. Nothing else is copied.
  • Its own permissions for notifications, camera, microphone and location, and its own push subscription. Web Push and the Badging API work only here, not in Safari tabs (iOS 16.4 and later).
  • Its own identity. From iOS 16.4, WebKit combines the name the user chose with the manifest id "to uniquely identify the web app". A user can add the same site twice under different names, and each copy is a separate app with separate storage.
  • An entry in the app switcher, Spotlight and the App Library.

Removing an iOS web app

The user long-presses the icon, chooses the remove option and confirms the deletion. Because the web app's data store belongs to the app, deleting the app deletes its storage along with it. There's no way to keep data across a reinstall except on your server, or in cookies that happen to still be in Safari when the user adds the site again.

Safari on macOS: Add to Dock

Safari 17 on macOS Sonoma brought web apps to the Mac, and for any website. WebKit's announcement: "When a user clicks on a web app icon, the website always opens in its own window as a web app, even if the site does not have a manifest file (or legacy meta tags)."

Creating a Dock web app

The user chooses File > Add to Dock… (or Share > Add to Dock), enters a name and clicks Add. Afterwards, the web app's own Settings let the user change the name, the URL and the icon, according to Apple's support article. The app appears in the Dock and can be opened from the Dock, Launchpad and Spotlight. There's no event and no API. Install Prompts & Custom UI shows an instructions dialog for this case.

What gets created

  • A real .app bundle in the user's ~/Applications folder. Apple's support article: "Web apps are saved to the Applications folder of your home folder."
  • A separate website data store. Apple: "A web app functions independently of Safari. It shares no browsing history, cookies, website data, or settings with Safari." At creation, though, WebKit copies the cookies: "When a user adds a website to their Dock, Safari will copy the website's cookies to the web app. […] This will only work if the authentication state is stored within cookies. Safari does not copy over any other kind of local storage."
  • System-level permissions. Users grant camera, microphone and location "in the same way they grant such permissions to other Mac apps through system prompts and the Privacy & Security section of System Settings". Web Push and badging work, and notifications must be allowed inside the web app, not in Safari.
  • Per-app settings. From the app's menu bar, Settings lets the user change the name, URL and icon, whether the toolbar shows navigation controls (back, forward, app name, Share and extension buttons), and whether the title bar takes the website's color. Since Safari 18, users can enable Safari Web Extensions and Content Blockers per web app.

The manifest customizes the presentation: WebKit names "the display mode, name, theme color, and start URL", and Safari 17.4 added shortcuts.

WebKit's Safari 18 announcement: "Now, when a user clicks a link, if it matches the scope of a web app, that link will open in the web app instead of their default web browser." This applies to links opened from other apps. When the user browses in Safari itself, matching links show an Open in web app banner, which the user can dismiss. Without a scope, the default scope is the host of the page the app was created from. Declare scope explicitly so that a Dock app created from a marketing page doesn't claim your whole domain.

Removing a Dock web app

Apple's instructions are to drag the app from ~/Applications to the Trash. Before doing that, a user who wants a clean removal can open the web app's Settings, go to the Privacy tab and clear the website data, "including cookies and caches". Deleting the .app bundle alone isn't documented to delete the data store, so treat the data as potentially orphaned.

Firefox on the desktop

For most of its history desktop Firefox couldn't install web apps, and MDN's installability guide still states that Firefox "does not support installing PWAs using a manifest file". That describes manifest-driven installation. Firefox does now have user-created web apps on one platform:

  • Firefox 143 (September 16, 2025): "On Windows, Firefox now supports running websites as web apps pinned directly to the taskbar." The release notes add that pinned sites run as simplified windows "without losing access to your installed add-ons", and that the feature wasn't available in the Microsoft Store build.
  • Firefox 150 (April 21, 2026) extended web apps to Windows users who installed Firefox from the Microsoft Store.

Firefox's source documentation, which calls the feature Taskbar Tabs, gives the platform status as of September 2026: web apps "are available on Windows (both MSIX and non-MSIX, enabled by default) and Linux (unsandboxed and under Flatpak, disabled by default)", controlled by the browser.taskbarTabs.enabled preference. macOS isn't supported, and the documentation explains why: the Dock assumes that two visible applications are two processes, but Firefox web apps run in the same profile and process as the browser, so the Dock can't tell them apart.

Other details from the same documentation matter for your code:

  • Identity. Firefox keeps each web app in taskbartabs/taskbartabs.json inside the profile, keyed by a Firefox-generated identifier, not by your manifest id. According to Firefox's source, it takes the scope, start_url and name from your manifest when present and otherwise falls back to the page's origin. App Identity & Updates has the storage details.
  • Containers. A web app is tied to the container (Contextual Identity) it was created in, so a user can keep a work and a personal copy of the same site isolated without separate profiles.
  • No developer hooks. There's no beforeinstallprompt, no appinstalled and no documented manifest update pipeline.
  • Shared storage. Because web apps live in the user's Firefox profile, they share storage with that profile's tabs (within the same container).

For your code, treat desktop Firefox as a browser, not an install target: don't depend on manifest-only features there, and detect the app window with the display-mode media feature rather than assuming.

Managed installs for organizations

Every flow above starts with a user. In managed environments, Chrome and Edge can also install web apps silently through the WebAppInstallForceList enterprise policy, supported in Chrome on Windows, macOS, Linux and ChromeOS since version 75. Chromium's policy definition describes it as "a list of web apps that install silently, without user interaction, and which users can't uninstall or turn off". The policy is per profile and refreshes dynamically, so adding an entry installs the app for signed-in users without a browser restart.

Each list entry is an object with one required member, url, and six optional ones:

Member Meaning Availability
url The page to install from; Chrome loads it and reads its manifest Required
default_launch_container "tab" (the default) or "window" All desktop platforms
create_desktop_shortcut true to create a Windows or Linux desktop shortcut Windows, Linux
fallback_app_name Name used when the page isn't a PWA, or temporarily while authentication blocks reading the manifest Chrome 90+
custom_name Permanently overrides the app name; takes precedence over fallback_app_name ChromeOS 99+, other desktops 112+
custom_icon { "url", "hash" }: a square icon of at most 1 MB (JPEG, PNG, GIF, WebP or ICO) and its SHA-256 hash; the URL must work without authentication ChromeOS 99+, other desktops 112+
install_as_shortcut Installs the URL as if the user chose Create shortcut; such shortcuts aren't updated when the manifest changes Chrome 107+

On Linux, Chrome reads managed policies from JSON files in /etc/opt/chrome/policies/managed/:

/etc/opt/chrome/policies/managed/web-apps.json
{
  "WebAppInstallForceList": [
    {
      "url": "https://app.example.com/",
      "default_launch_container": "window",
      "create_desktop_shortcut": true,
      "fallback_app_name": "Example Orders"
    },
    {
      "url": "https://intranet.example.com/wiki/",
      "default_launch_container": "window",
      "install_as_shortcut": true,
      "custom_name": "Wiki"
    }
  ]
}

On Windows and macOS, administrators set the same list through Group Policy, Intune or configuration profiles, and ChromeOS administrators use the Google Admin console. Microsoft Edge supports a policy with the same name and structure, documented in Microsoft's Edge policy reference. For your web app, three details follow:

  • The install URL is fetched by the browser with the user's session. If url requires authentication, Chrome can't read your manifest yet and temporarily installs an app named after fallback_app_name until the installation can be completed. Give administrators a landing page inside your scope that serves the manifest link without a sign-in redirect.
  • The installed app is identified by your manifest id, exactly as a user install is, so a manifest id change orphans force-installed apps the same way.
  • Users can't remove a policy-installed app. Uninstalling is under the administrator's control, through the policy, which also means the "Uninstalling and data retention" behavior below doesn't apply to these users.

Where installed app data lives

The single most important platform difference for application code is whether the installed app shares storage with the browser.

Platform Storage owner Shared with browser tabs? What survives installation
Chrome, Edge desktop Browser profile Yes, same profile Everything
Chrome Android (WebAPK) Chrome profile Yes Everything
Samsung Internet (WebAPK) Samsung Internet profile Yes, but not with Chrome Everything in Samsung Internet
Firefox Android shortcut Firefox profile Yes Everything in Firefox
Firefox Windows web app Firefox profile Yes Everything in Firefox
iOS and iPadOS web app The web app's own container No Cookies only (iOS 17.2+), copied once
Safari macOS Dock app The web app's own container No Cookies only, copied once
flowchart LR
    subgraph Chromium["Chromium (desktop, Android)"]
        T1["Browser tab"] --> P1[("Profile storage")]
        A1["Installed app window"] --> P1
    end
    subgraph Apple["Safari (iOS, iPadOS, macOS)"]
        T2["Safari tab"] --> S2[("Safari storage")]
        S2 -. "cookies copied once at creation" .-> W2[("Web app container")]
        A2["Web app"] --> W2
    end

On Chromium, your service worker, precache and IndexedDB are already populated when the user launches the app for the first time, which makes the first launch fast and offline-capable immediately. On Safari, the first launch is a cold start: the service worker has to register and install again, caches are empty, and any client-side state (a cart in localStorage, a draft in IndexedDB, onboarding progress) is gone.

Carrying client-side state across Safari's install boundary

Because cookies are the only thing Safari copies, the reliable way to move state into a newly created web app is to put it on your server, keyed by the session cookie, before the user installs. The pattern below saves a small handoff record when the user opens your install instructions and restores it on the first standalone launch.

install-handoff.js
// Moves client-side state into a Safari web app (iOS, iPadOS, macOS Dock apps),
// which receives only a copy of the site's cookies when it's created.
// Requires a cookie-based session and the two endpoints described below.

const HANDOFF_URL = "/api/install-handoff";
const RESTORED_FLAG = "install-handoff-restored";

/** True when this document runs as an installed app window. */
export function isStandalone() {
  return (
    // Check first: on iOS a manifest "standalone" app matches
    // display-mode: fullscreen (WebKit bug 264218), and an app added without
    // a manifest reports "browser". True in iOS/iPadOS Home Screen apps and,
    // since Safari 17, in macOS Dock apps.
    navigator.standalone === true ||
    matchMedia("(display-mode: standalone)").matches ||
    matchMedia("(display-mode: fullscreen)").matches
  );
}

/**
 * Call when you show Add to Home Screen / Add to Dock instructions.
 * `collectState` returns a small JSON-serializable snapshot (cart, draft id, step).
 */
export async function saveHandoff(collectState) {
  const state = await collectState();
  const body = JSON.stringify({ state, savedAt: Date.now() });
  if (body.length > 16_384) {
    // Keep handoffs small: store large drafts server-side and hand off their ids.
    throw new Error("Install handoff state is too large");
  }
  const response = await fetch(HANDOFF_URL, {
    method: "PUT",
    credentials: "same-origin", // the session cookie identifies the user
    headers: { "Content-Type": "application/json" },
    body,
    keepalive: true, // survives the user switching to the Share sheet
  });
  if (!response.ok) throw new Error(`Handoff save failed: ${response.status}`);
}

/**
 * Call once at startup. In a fresh Safari web app, local storage is empty but the
 * copied session cookie is present, so the server can return the saved snapshot.
 */
export async function restoreHandoff(applyState) {
  if (!isStandalone()) return false;

  let alreadyRestored = false;
  try {
    alreadyRestored = localStorage.getItem(RESTORED_FLAG) === "1";
  } catch {
    // Storage can be unavailable; fall through and ask the server.
  }
  if (alreadyRestored) return false;

  const response = await fetch(HANDOFF_URL, {
    method: "GET",
    credentials: "same-origin",
    cache: "no-store", // never let the service worker or HTTP cache answer this
  });
  if (response.status === 204 || response.status === 404) return false;
  if (!response.ok) throw new Error(`Handoff restore failed: ${response.status}`);

  const { state } = await response.json();
  await applyState(state);

  try {
    localStorage.setItem(RESTORED_FLAG, "1");
  } catch {
    // Not fatal: the server deletes the record after the first successful GET.
  }
  return true;
}

The server side needs two routes: PUT stores the snapshot against the session with a short expiry (an hour is plenty), and GET returns it once and deletes it, answering 204 when there's nothing to restore. On Chromium, restoreHandoff() finds nothing because the app shares storage with the tab, so the same code is harmless everywhere. Exclude the endpoint from service worker caching, or handle it network-only; see Caching Strategies.

Uninstalling and data retention compared

Platform How users uninstall Site data after uninstall Your code notified?
Chrome, Edge desktop App menu, chrome://apps, Installed apps on Windows Kept unless the user ticks the delete-data checkbox No
ChromeOS Launcher right-click > Uninstall Kept unless Also delete browsing data is ticked No
Chrome Android (WebAPK) Long-press > Uninstall, Settings > Apps Kept in Chrome No
Android shortcuts Remove from home screen Kept in the browser No
iOS, iPadOS Long-press > remove, confirm Deleted with the app No
Safari macOS Drag the .app to the Trash Not documented as deleted; clear it in the app's Privacy settings first No
Firefox on Windows Unpin or remove the web app in Firefox Kept in the Firefox profile No

Since no uninstall event exists anywhere, infer uninstalls on the server: push endpoints that start answering 404 or 410, or installed-app sessions (tagged through your start_url) that stop arriving. Detecting Installed Apps has the server-side tracking code, and The Web Push Protocol explains the push service responses.

A subtle Chromium case: the user uninstalls the app but keeps the data, then reinstalls. The service worker, caches and IndexedDB are still there, and the "new" install starts with old state and possibly an old service worker version. Make your startup code tolerate any previous schema version, as you would for a user who hasn't opened a tab in months; see Updating Service Workers.

Browser support

Support data as of September 2026. For live data, see MDN's Making PWAs installable and caniuse: Web App Manifest.

Capability Chrome/Edge desktop Chrome Android Samsung Internet Firefox Android Firefox Windows Safari iOS/iPadOS Safari macOS
Install from the manifest ✅ ✅ ✅ ✅ ⚠️1 ✅ ✅
Install any site without a manifest ✅ ✅ ❌ ❌ ✅ ✅ 26 ✅ 17
Real OS app entry ✅ ✅ WebAPK ✅ WebAPK2 ❌ shortcut ⚠️ taskbar pin ✅ ✅
beforeinstallprompt / appinstalled ✅ ✅ ✅ ❌ ❌ ❌ ❌
OS link capturing into the app ✅ 1403 ✅ intent filters ✅ ❌ ❌ ❌ ✅ 18
Manifest updates applied ✅ ✅ (slow) ✅ ❌ ❌ ❌ ❌
Storage isolated from the browser ❌ ❌ ❌ ❌ ❌ ✅ ✅
Uninstall can delete site data ✅ opt-in ❌ ❌ ❌ ❌ ✅ always ⚠️ manual

Debugging installs on each platform

  • Chrome and Edge desktop. DevTools Application > Manifest shows the parsed manifest, the computed app ID and installability errors. chrome://web-app-internals dumps the web app system's state, including every installed app, its OS integration and pending updates. chrome://apps lists installed apps per profile. See Browser DevTools.
  • Chrome Android. Use remote debugging from chrome://inspect on a computer, about://webapks on the device, and the adb script above. If the app appears only as a shortcut, check that the device has Google Play services and that the installability check passes in DevTools.
  • Samsung Internet. Test on a Samsung device, since WebAPK minting and the install UI exist only there. Compare the result with Chrome on the same device, and check Settings > Apps to confirm whether you got a WebAPK or a shortcut.
  • iOS and iPadOS. Connect the device to a Mac and use Safari's Develop menu; each Home Screen web app appears as its own inspectable target. Since each web app has its own storage, delete and re-add the app to test a first launch.
  • Safari on macOS. Inspect a running Dock web app from Safari's Develop menu. Remove and re-add the app to test a first launch with empty storage.
  • Windows OS integration. Settings > Apps > Installed apps shows whether the uninstall entry was created; the Start menu shows the shortcut; file and protocol handlers appear under Default apps.

Common pitfalls

  • Telling Android users to "Add to home screen". Depending on the Chrome version and the site, that entry creates a shortcut instead of a WebAPK. Point them to Install, or show your own install button.
  • Assuming the icon exists when appinstalled fires on Android. WebAPK minting happens afterward. Word your confirmation as "Installing…" or wait for the next launch in standalone mode.
  • Expecting a signed-in session in an iOS or macOS Safari web app when the session lives in localStorage. Only cookies are copied, once. Move session tokens to cookies or use the handoff pattern above.
  • Declaring scope: "/" on a shared domain. The WebAPK's intent filter, Chrome's desktop navigation capturing and Safari 18's link handling all capture every link under the scope.
  • Shipping a manifest id late. Desktop Chromium, WebAPKs and Safari all key the installed app to an identity computed at install time; adding or changing id later creates a second app. Set it before launch; see App Identity & Updates.
  • Treating uninstall as data deletion. On Chromium and Android the origin's storage usually survives. Design data retention and account deletion flows on the server.
  • Assuming the browser that installed the app runs it on iOS. Chrome for iOS creates a WebKit web app that doesn't open in Chrome or share Chrome's state after creation; whether it receives cookies at creation isn't documented.
  • Relying on Firefox desktop install features. Only Windows has web apps by default (Linux behind a preference), created by the user, with no install events and no manifest-driven integrations.

Further reading

On this site

External references


  1. Firefox on Windows reads a few manifest members (scope, start_url, name) when creating a web app, but installation is user-initiated and the web app isn't keyed by the manifest id. ↩

  2. On Samsung devices only. On other Android devices Samsung Internet creates a shortcut. ↩

  3. On by default on Windows, macOS and Linux: Chrome 138 for apps that declare launch_handler.client_mode, Chrome 140 for all apps. Only navigations that would open a new tab or window are captured. On ChromeOS it is available but off by default per app. ↩