Ship a PWA for iOS
Published
Safari lets users add a PWA to the iOS Home Screen. Prepare its manifest and add-flow instructions, detect the display mode, and request notification permission from a user action. Permission alone does not complete a push subscription.
Platform support
Section titled “Platform support”Safari 26 on iOS 26 opens a site added to the Home Screen as a web app by default, while the user can disable Open as Web App to create a bookmark. WebKit documents Web Push for Home Screen web apps on iOS and iPadOS 16.4. Its third-party-browser add path requires Apple’s managed web-browser entitlement.
Provide a manifest
Section titled “Provide a manifest”A web app manifest supplies installation metadata such as the app name and icons. The
display member expresses a preferred display mode, and start_url is a hint that a
browser may ignore. Safari 26 uses manifest icons when they are present.
{ "name": "Field Notes", "short_name": "Notes", "start_url": "/", "display": "standalone", "icons": [ { "src": "/icons/notes-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/notes-512.png", "sizes": "512x512", "type": "image/png" } ]}Link the manifest from the document head with
<link rel="manifest" href="/manifest.webmanifest">.
Explain the Safari add flow
Section titled “Explain the Safari add flow”Apple’s iPhone guide instructs the user to open Safari’s Share menu, choose Add to Home Screen, turn on Open as Web App, and choose Add. Present those instructions when the document is not in standalone display mode.
Request notification permission from an action
Section titled “Request notification permission from an action”WebKit states that a Home Screen web app on iOS or iPadOS 16.4 must request Web Push
permission in response to direct user interaction. MDN likewise places Notification.requestPermission()
in a click handler. Permission is one step; WebKit describes standards-based Web Push as
the Push API, Notifications API, and Service Workers working together.
Examples
Section titled “Examples”These complete pages cover the display-mode branch and the permission branch. Both keep a visible fallback when the tested capability or state is absent.
Show the Safari instruction outside standalone mode
Section titled “Show the Safari instruction outside standalone mode”Use the display-mode media feature to hide the instruction in standalone
mode. In every other display mode, the documented Safari route stays visible.
<!doctype html><html lang="en"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Install Field Notes</title> <p id="install-hint">In Safari, use Share, then Add to Home Screen.</p> <script> const installHint = document.querySelector('#install-hint'); const standalone = window.matchMedia('(display-mode: standalone)').matches;
if (standalone) { installHint.hidden = true; } else { installHint.hidden = false; } </script></html>MDN notes that display mode does not directly prove installation status, so this example uses it only to decide whether the instruction belongs in the current view.
Request notification permission accurately
Section titled “Request notification permission accurately”Report only the permission result. If the Notifications API is absent or the
result is not granted, keep an in-page updates path available.
<!doctype html><html lang="en"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Field Notes notification permission</title> <button id="permission" type="button">Request notification permission</button> <p id="status" aria-live="polite">Updates are available in the app.</p> <script> const status = document.querySelector('#status');
document.querySelector('#permission').addEventListener('click', async () => { if (!('Notification' in window)) { status.textContent = 'Updates remain available in the app.'; return; }
const permission = await Notification.requestPermission(); if (permission === 'granted') { status.textContent = 'Notification permission granted.'; } else { status.textContent = 'Updates remain available in the app.'; } }); </script></html>The success text does not claim that push is enabled; the example does not create a Push API subscription.