Skip to content

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.

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.

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">.

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.

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.