# Ship a PWA for iOS

> A source-backed iOS PWA checklist covering the manifest, Safari's Home Screen flow, display-mode detection, and notification permission.

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

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

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.

```json
{
  "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

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

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

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

Use the `display-mode` media feature to hide the instruction in standalone
mode. In every other display mode, the documented Safari route stays visible.

```html
<!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

Report only the permission result. If the Notifications API is absent or the
result is not `granted`, keep an in-page updates path available.

```html
<!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.

:::caution
WebKit requires the Web Push permission request on iOS and iPadOS 16.4 to follow direct
user interaction. Do not call it during page load.
:::

## See also

- [Turn a website into an app in Safari on iPhone (Apple Support)](https://support.apple.com/guide/iphone/open-as-web-app-iphea86e5236/ios)
- [Web Push for Web Apps on iOS and iPadOS (WebKit)](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)
- [Make it installable](/guides/installable/)
- [PWAs on iOS and Safari](/reference/platforms/ios-safari/)
- [iOS Add to Home Screen](/reference/installation/ios-add-to-home-screen/)