# Progressive Web Apps

# index

---
layout: collection
override:tags: []
date: 2020-02-24
pathName: progressive-web-apps
---

# Add a web app manifest

The web app manifest is a JSON file that tells the browser about your
Progressive Web App and how it should behave when installed on the user's
desktop or mobile device. A typical manifest file includes the app name, the
icons the app should use, and the URL that should be opened when the
app is launched, among other things.
{% BrowserCompat 'html.manifest.name' %}

## Create the manifest file {: #create }

The manifest file can have any name, but is commonly named `manifest.json` and
served from the root (your website's top-level directory). The specification
suggests the extension should be `.webmanifest`, but browsers also support
`.json` extensions, which may be easier for developers to understand.

A typical manifest looks something like this:

```json
{
  "short_name": "Weather",
  "name": "Weather: Do I need an umbrella?",
  "icons": [
    {
      "src": "/images/icons-vector.svg",
      "type": "image/svg+xml",
      "sizes": "512x512"
    },
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "id": "/?source=pwa",
  "start_url": "/?source=pwa",
  "background_color": "#3367D6",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#3367D6",
  "shortcuts": [
    {
      "name": "How's weather today?",
      "short_name": "Today",
      "description": "View weather information for today",
      "url": "/today?source=pwa",
      "icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
    },
    {
      "name": "How's weather tomorrow?",
      "short_name": "Tomorrow",
      "description": "View weather information for tomorrow",
      "url": "/tomorrow?source=pwa",
      "icons": [{ "src": "/images/tomorrow.png", "sizes": "192x192" }]
    }
  ],
  "description": "Weather forecast information",
  "screenshots": [
    {
      "src": "/images/screenshot1.png",
      "type": "image/png",
      "sizes": "540x720",
      "form_factor": "narrow"
    },
    {
      "src": "/images/screenshot2.jpg",
      "type": "image/jpg",
      "sizes": "720x540",
      "form_factor": "wide"
    }
  ]
}
```

### Key manifest properties {: #manifest-properties }

#### `short_name` and/or `name` {: #name }

You must provide at least the `short_name` or `name` property. If both are
provided, `short_name` is used on the user's home screen, launcher, or other
places where space may be limited. `name` is used when the app is installed.

{% Aside %}
Operating systems usually expect to have a title for each app window. This
title is displayed in various window-switching surfaces such as
<kbd>alt</kbd>+<kbd>tab</kbd>, overview mode, and the shelf window list.

For PWAs running in standalone mode, Chromium prepends the `short_name`
(or, if it's not available, the `name`) to what is
specified in the `<title>` of the HTML document to prevent disguise attacks
where standalone apps might try to be mistaken, for example, for operating
system dialogs.

In consequence, developers should _not_ repeat the
application name in the `<title>` when the app is running in standalone mode.
{% endAside %}

#### `icons` {: #icons }

When a user installs your PWA, you can define a set of icons for the browser
to use on the home screen, app launcher, task switcher, splash screen, and so on.

The `icons` property is an array of image objects. Each object must
include the `src`, a `sizes` property, and the `type` of image. To use
[maskable icons](/maskable-icon/), sometimes referred to as adaptive
icons on Android, you'll also need to add `"purpose": "any maskable"` to the
`icon` property.

For Chromium, you must provide at least a 192x192 pixel icon, and a 512x512
pixel icon. If only those two icon sizes are provided, Chrome
automatically scales the icons to fit the device. If you'd prefer to scale your
own icons, and adjust them for pixel-perfection, provide icons in increments
of 48dp.

{% Aside %}
Chromium-based browsers also support SVG icons which can be scaled arbitrarily
without looking pixelated and that support advanced features like
[responsiveness to `prefers-color-scheme`](https://blog.tomayac.com/2021/07/21/dark-mode-web-app-manifest-app-icons/),
with the caveat that the icons do not update live, but remain in the
state they were in at install time.

To use SVG icons safely, you should always specify a rasterized icon as a
fallback for browsers that do not support SVG icons.
{% endAside %}

#### `id` {: #id }

The `id` property allows you to explicitly define the identifier used for your application. Adding the `id` property to the manifest removes the dependency on the `start_url` or the location of the manifest, and makes it possible for them to be updated in the future. For more information, see [Uniquely identifying PWAs with the web app manifest id property](https://developer.chrome.com/blog/pwa-manifest-id/).

#### `start_url` {: #start-url }

The `start_url` is required and tells the browser where your application
should start when it is launched, and prevents the app from starting on
whatever page the user was on when they added your app to their home screen.

Your `start_url` should direct the user straight into your app, rather than
a product landing page. Think about what the user will want to do once
they open your app, and place them there.

#### `background_color` {: #background-color }

The `background_color` property is used on the splash screen when the
application is first launched on mobile.

#### `display` {: #display }

You can customize what browser UI is shown when your app is launched. For
example, you can hide the address bar and browser
user interface elements. Games can even
be made to launch full screen. The `display` property takes one of the following values:

<div class="table-wrapper">
  <table id="display-params">
    <thead>
      <tr>
        <th><strong>Property</strong></th>
        <th><strong>Use</strong></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code>fullscreen</code></td>
        <td>
          Opens the web application without any browser UI and takes
          up the entirety of the available display area.
        </td>
      </tr>
      <tr>
        <td><code>standalone</code></td>
        <td>
          Opens the web app to look and feel like a standalone
          app. The app runs in its own window, separate from the browser, and
          hides standard browser UI elements such as the URL bar.
          <figure>
            {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png", alt="An example of a PWA window with standalone display.", width="800", height="196" %}
          </figure>
        </td>
      </tr>
      <tr>
        <td><code>minimal-ui</code></td>
        <td>
          This mode is similar to <code>standalone</code>, but provides the
          user a minimal set of UI elements for controlling navigation (such
          as back and reload).
          <figure>
            {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png", alt="An example of a PWA window with minimal-ui display.", width="800", height="196" %}
          </figure>
        </td>
      </tr>
      <tr>
        <td><code>browser</code></td>
        <td>A standard browser experience.</td>
      </tr>
    </tbody>
  </table>
</div>

#### `display_override` {: #display-override }

Web apps can choose how they are displayed by setting a `display` mode in their manifest as
[explained above](#display). Browsers are *not* required to support all display modes, but they
*are* required to support the
[spec-defined fallback chain](https://w3c.github.io/manifest/#dfn-fallback-display-mode)
(`"fullscreen"` → `"standalone"` → `"minimal-ui"` → `"browser"`). If they don't support a given
mode, they fall back to the next display mode in the chain. This inflexible behavior can be
problematic in rare cases. For example, a developer cannot request `"minimal-ui"` without being
forced back into the `"browser"` display mode when `"minimal-ui"` is not supported.
Another problem is that the current behavior makes it impossible to introduce new display
modes in a backward compatible way, since explorations like tabbed application mode don't have a
natural place in the fallback chain.

These problems are solved by the `display_override` property, which the browser considers *before*
the `display` property. Its value is a sequence of strings that are considered in the listed order, and the
first supported display mode is applied. If none are supported, the browser falls back to evaluating
the `display` field.

Consider the example below. (The details of
[`"window-control-overlay"`](/window-controls-overlay/) are out-of-scope for
this article.)

```json
{
  "display_override": ["window-control-overlay", "minimal-ui"],
  "display": "standalone",
}
```

As stated, the browser will look at `display_override` first.

1. `"window-control-overlay"`
1. `"minimal-ui"`

If neither option is available, it falls back to `display`. If `"standalone"` is
not available, it resumes spec-defined fallabck chain from that point.

1. `"standalone"`
1. `"minimal-ui"`
1. `"browser"`

{% Aside %}
  The browser will not consider `display_override` unless `display` is also present.
{% endAside %}

#### `scope` {: #scope }

The `scope` defines the set of URLs that the browser considers to be within your
app, and is used to decide when the user has left the app. The `scope`
controls the URL structure that encompasses all the entry and exit points in
your web app. Your `start_url` must reside within the `scope`.

{% Aside 'caution' %}
If the user clicks a link in your app that navigates outside of the
`scope`, the link opens and renders within the existing PWA window. If
you want the link to open in a browser tab, you must add `target="_blank"`
to the `<a>` tag. On Android, links with `target="_blank"` open in a
[Chrome Custom Tab](https://developer.chrome.com/multidevice/android/customtabs).
{% endAside %}

A few other notes on `scope`:

* If you don't include a `scope` in your manifest, then the default implied
  `scope` is the directory that your web app manifest is served from.
* The `scope` attribute can be a relative path (`../`), or any higher level
  path (`/`) which would allow for an increase in coverage of navigations
  in your web app.
* The `start_url` must be in the scope.
* The `start_url` is relative to the path defined in the `scope` attribute.
* A `start_url` starting with `/` will always be the root of the origin.

#### `theme_color` {: #theme-color }

The `theme_color` sets the color of the tool bar, and may be reflected in
the app's preview in task switchers. The `theme_color` should match the
`meta` theme color specified in your document head.


<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png", alt="An example of a PWA window with custom theme_color.", width="800", height="196" %}
  <figcaption>
    An example of a PWA window with custom theme_color.
  </figcaption>
</figure>

As of Chromium&nbsp;93 and Safari&nbsp;15, you can adjust this color in a
media query with the `media` attribute of the `meta` theme color element. The
first one that matches will be picked. For example, you could have one color for
light mode and another one for dark mode. At the time of writing, you can't
define those in your manifest. See [w3c/manifest#975 GitHub
issue](https://github.com/w3c/manifest/issues/975).

```html
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)"  content="black">
```

#### `shortcuts` {: #shortcuts }

The `shortcuts` property is an array of [app shortcut](/app-shortcuts) objects
whose goal is to provide quick access to key tasks within your app. Each member
is a dictionary that contains at least a `name` and a `url`.

#### `description` {: #description }

The `description` property describes the purpose of your app.

#### `screenshots` {: #screenshots }

The `screenshots` property is an array of image objects representing your app
in common usage scenarios. Each object must include the `src`, a `sizes`
property, and the `type` of image.  The `form_factor` property is optional.
You can set it either to `"wide"` for screenshots applicable to wide screens
only or `"narrow"` for narrow screenshots. You should only use it when the
layout varies by screen size.

In Chrome, the image must respond to certain criteria:

* Width and height must be at least 320px and at most 3840px.
* The maximum dimension can't be more than 2.3 times as long as the minimum
  dimension.
* All screenshots matching the appropriate form factor must have the same
  aspect ratio.

<figure>
  {% Img src="image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg", alt="Screenshots of richer installation UI on desktop and mobile", width="800", height="386" %}
  <figcaption>
    Richer installation UI on desktop and mobile.
  </figcaption>
</figure>

## Add the web app manifest to your pages {: #link-manifest }

After creating the manifest, add a `<link>` tag to all the pages of your
Progressive Web App. For example:

```html
<link rel="manifest" href="/manifest.json">
```

{% Aside 'gotchas' %}
The request for the manifest is made **without** credentials (even if it's
on the same domain), thus if the manifest requires credentials, you must
include `crossorigin="use-credentials"` in the manifest tag.
{% endAside %}

## Test your manifest {: #test-manifest }

To verify your manifest is setup correctly, use the **Manifest** pane in the
**Application** panel of Chrome DevTools.


<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png", alt="The application panel in Chrome Devtools with the manifest tab selected.", width="800", height="601" %}
  <figcaption>
    Test your manifest in DevTools.
  </figcaption>
</figure>

<figure>

</figure>

This pane provides a human-readable version of many of your manifest's
properties, and makes it easy to verify that all of the images are loading
properly.

## Splash screens on mobile {: #splash-screen }

When your app first launches on mobile, it can take a moment for the browser
to spin up, and the initial content to begin rendering. Instead of showing a
white screen that may look to the user like the app is stalled, the browser
will show a splash screen until the first paint.

Chrome automatically creates the splash screen from the manifest
properties, specifically:

* `name`
* `background_color`
* `icons`

The `background_color` should be the same color as the load page, to provide
a smooth transition from the splash screen to your app.

Chrome will choose the icon that closely matches the device resolution for the
device. Providing 192px and 512px icons is sufficient for most cases, but
you can provide additional icons for pixel perfection.

## Further reading

There are several additional properties that can be added to the web app
manifest. Refer to the [MDN Web App Manifest documentation][mdn-manifest]
for more information.

[mdn-manifest]: https://developer.mozilla.org/docs/Web/Manifest

# Make your PWA feel more like an app

When you play Progressive Web App buzzword bingo, it is a safe bet to set on "PWAs are just websites". Microsoft's PWA documentation [agrees](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/#progressive-web-apps-on-windows:~:text=PWAs%20are%20just%20websites), we [say it](/progressive-web-apps/#content:~:text=Progressive%20Web%20Apps,Websites) on this very site, and even PWA nominators Frances Berriman and Alex Russell [write so](https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/#post-2263:~:text=they%E2%80%99re%20just%20websites), too. Yes, PWAs are just websites, but they are also way more than that. If done right, a PWA will not feel like a website, but like a "real" app. Now what does it mean to feel like a real app?

To answer this question, let me use the Apple [Podcasts](https://support.apple.com/en-us/HT201859) app as an example.
It is available on macOS on desktop and on iOS (and iPadOS respectively) on mobile.
While Podcasts is a media application, the core ideas I illustrate with its help apply to other categories of apps, too.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png", alt="An iPhone and a MacBook side by side, both running the Podcasts application.", width="800", height="617" %}
  <figcaption>Apple Podcasts on iPhone and on macOS (<a href="https://support.apple.com/en-us/HT201859">Source</a>).</figcaption>
</figure>

{% Aside 'caution' %}
Each iOS/Android/desktop app feature that is listed below has a **How to do this on the web**
component that you can open for more details.
Note that not all browsers on the various operating systems support all the listed APIs
or functionalities. Be sure to carefully review the compatibility notes in the linked articles.
{% endAside %}

## Capable of running offline

If you take a step back and think of the platform-specific applications you have on your mobile phone or desktop computer, one thing clearly stands out: you never get nothing. In the Podcasts app, even if I am offline, there is always something. When there is no network connection, the app naturally still opens. The **Top Charts** section does not show any content, but instead falls back to a **Can't connect right now** message paired with a **Retry** button. It may not be the most welcoming experience, but I get something.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png", alt="The Podcasts app showing a 'Cannot connect right now.' info message when no network connection is available.", width="800", height="440" %}
  <figcaption>Podcasts app without network connection.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The Podcasts app follows the so-called app shell model. All the static content that is needed to show the core app is cached locally,
  including decorative images like the left-hand menu icons and the core player UI icons.
  Dynamic content like the <b>Top Charts</b> data is only loaded on demand, with locally cached fallback content available should the loading fail.
  Read the  article <a href="https://developers.google.com/web/fundamentals/architecture/app-shell">The App Shell Model</a>
  to learn how to apply this architectural model to your web app.
{% endDetails %}

## Offline content available and media playable

While offline, via the left-hand drawer, I can still navigate to the **Downloaded** section and enjoy downloaded podcast episodes that are ready to be played
and are displayed with all metadata like artwork and descriptions.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png", alt="Podcasts app with a downloaded episode of a podcast playing.", width="800", height="440" %}
  <figcaption>Downloaded podcast episodes can be played even without network.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Previously downloaded media content can be served from the cache, for example using the
  <a href="https://developer.chrome.com/docs/workbox/serving-cached-audio-and-video/">Serve cached audio and video</a>
  recipe from the <a href="https://developer.chrome.com/docs/workbox/">Workbox</a> library.
  Other content can always be stored in the cache, or in IndexedDB. Read the article <a href="/storage-for-the-web/">Storage for the web</a>
  for all details and to know when to use what storage technology.
  If you have data that should be persistently stored without the risk of being purged when the
  available amount of memory gets low, you can use the
  <a href="/persistent-storage/">Persistent Storage API</a>.
{% endDetails %}

## Proactive background downloading

When I am back online, I can of course search for content with a query like `http 203`, and when I decide to subscribe to the search result, the [HTTP 203 podcast](/podcasts/), the latest episode of the series is immediately downloaded, no questions asked.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png", alt="The Podcasts app downloading the latest episode of a podcast immediately after subscribing.", width="800", height="658" %}
  <figcaption>After subscribing to a podcast, the latest episode is immediately downloaded.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Downloading a podcast episode is an operation that potentially can take longer. The <a href="https://developers.google.com/web/updates/2018/12/background-fetch">Background Fetch API</a> lets you delegate downloads to the browser, which takes care of them in the background.
  On Android, the browser in turn can even delegate these downloads further on to the operating system, so the browser does not need to be continuously running.
  Once the download has completed, your app's service worker gets woken up and you can decide what to do with the response.
{% endDetails %}

## Sharing to and interacting with other applications

The Podcasts app integrates naturally with other applications. For example, when I right-click an episode that I like, I can share it to other apps on my device, like the Messages app. It also naturally integrates with the system clipboard. I can right-click any episode and copy a link to it.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png", alt="The Podcasts app's context menu invoked on a podcast episode with the 'Share Episode > Messages' option selected.", width="800", height="392" %}
  <figcaption>Sharing a podcast episode to the Messages app.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="/web-share/">Web Share API</a> and the <a href="/web-share-target/">Web Share Target API</a>
  allow your app to share and receive texts, files, and links to and from other applications on the device.
  Although it is not yet possible for a web app to add menu items to the operating system's built-in right-click menu, there are lots of other ways to link to and from other apps on the device.
  With the <a href="/image-support-for-async-clipboard/">Async Clipboard API</a>, you can programmatically read and write
  text and image data (PNG images) to the system clipboard.
  On Android, you can use the <a href="/contact-picker/">Contact Picker API</a> to select entries from the device's contacts manager.
  If you offer both a platform-specific app and a PWA, you can use the <a href="/get-installed-related-apps/">Get Installed Related Apps API</a>
  to check if the platform-specific app is installed, in which case you do not need to encourage the user to install the PWA or accept web push notifications.
{% endDetails %}

## Background app refreshing

In the Podcasts app's settings, I can configure the app to download new episodes automatically. Like that, I do not even have to think about it, updated content will always just be there. Magic.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png", alt="The Podcasts app's settings menu in the 'General' section where the 'Refresh Podcasts' option is set to 'Every Hour'.", width="800", height="465" %}
  <figcaption>Podcasts configured to check for new podcast episode every hour.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="/periodic-background-sync/">Periodic Background Sync API</a>
  allows your app to refresh its content regularly in the background without the need for it to be running.
  This means new content is proactively available, so your users can start delving into it right away whenever they decide.
{% endDetails %}

## State synchronized over the cloud

At the same time, my subscriptions are synchronized across all devices I own. In a seamless world, I do not have to worry about manually keeping my podcast subscriptions in sync. Likewise, I do not have to be afraid that my mobile device's memory will be consumed by episodes I have already listened to on my desktop. The play state is kept in sync, and listened-to episodes are automatically deleted.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png", alt="The Podcasts app's settings menu in the 'Advanced' section where the 'Sync subscriptions across devices' option is activated.", width="800", height="525" %}
  <figcaption>State is synchronized over the cloud.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Syncing app state data is a task that you can delegate to the <a href="https://developers.google.com/web/updates/2015/12/background-sync">Background Sync API</a>. The sync operation itself does not have to happen immediately, just <em>eventually</em>, and maybe even when the user has closed the app again already.
{% endDetails %}

## Hardware media key controls

When I am busy with another application, say, reading a news page in the Chrome browser, I can still control the Podcasts app with the media keys on my laptop.
There is no need to switch to the app just to skip forward or backward.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png", alt="Apple MacBook Pro Magic Keyboard with annotated media keys.", width="800", height="406" %}
  <figcaption>The media keys allow for controlling the Podcasts app (<a href="https://support.apple.com/guide/macbook-pro/magic-keyboard-apdd0116a6a2/mac">Source</a>).</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Media keys are supported by the <a href="/media-session/">Media Session API</a>.
  Like that, users can make use of the hardware media keys on their physical keyboards, headphones, or even control the web app
  from the software media keys on their smartwatches.
  An additional idea to smooth seeking operations is to send a
  <a href="https://developer.mozilla.org/docs/Web/API/Vibration_API">vibration pattern</a>
  when the user seeks over a significant part of the content, for example, passing the opening credits or a chapter boundary.
{% endDetails %}

## Multitasking and app shortcut

Of course I can always multitask back to the Podcasts app from anywhere. The app has a clearly distinguishable icon that I can also put on my desktop or application dock so Podcasts can be launched immediately when I feel like it.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png", alt="The macOS task switcher with a number of app icons to choose from, one of them the Podcasts app.", width="800", height="630" %}
  <figcaption>Multitasking back to the Podcasts app.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Progressive Web Apps on both desktop and mobile can be installed to the home screen, start menu, or application dock.
  Installation can happen based on a proactive prompt, or fully controlled by the app developer.
  The article <a href="/install-criteria/">What does it take to be installable?</a> covers everything you need to know.
  When multitasking, PWAs appear independent from the browser.
{% endDetails %}

## Quick actions in context menu

The most common app actions, **Search** for new content and **Check for New Episodes**, are available right from the context menu of the app in the Dock. Via the **Options** menu, I can also decide to open the app at login time.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png", alt="Podcasts app icon context menu showing the 'Search' and 'Check for New Episodes' options.", width="534", height="736" %}
  <figcaption>Quick actions are immediately available right from the app icon.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  By specifying <a href="/app-shortcuts/">app icon shortcuts</a>
  in the PWA's web app manifest, you can register quick routes to common tasks that users can reach directly from the app icon.
  On operating systems like macOS, users can also right-click the app icon and set the app to launch at login time.
  There is ongoing work on a proposal for <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/RunOnLogin/Explainer.md">run on login</a>.
{% endDetails %}

## Act as default app

Other iOS applications and even websites or emails can integrate with the Podcasts app by leveraging the `podcasts://` URL scheme. If I follow a link like [`podcasts://podcasts.apple.com/podcast/the-css-podcast/id1042283903`](podcasts://podcasts.apple.com/podcast/the-css-podcast/id1042283903) while in the browser, I am brought right into the Podcasts app and can decide to subscribe or listen to the podcast.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png", alt="The Chrome browser showing a confirmation dialog asking the user whether they want to open the Podcasts app.", width="800", height="492" %}
  <figcaption>The Podcasts app can be opened right from the browser.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Handling fully custom URL schemes is not yet possible, but there is ongoing work on a proposal for
  <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/URLProtocolHandler/explainer.md">URL Protocol Handling</a>
  for PWAs. Currently, <a href="https://developer.mozilla.org/docs/Web/API/Navigator/registerProtocolHandler"><code>registerProtocolHandler()</code></a> with a <code>web+</code> scheme prefix is the best alternative.
{% endDetails %}

## Local file system integration

You may not immediately think of it, but the Podcasts app naturally integrates with the local file system. When I download a podcast episode, on my laptop it is stored in `~/Library/Group Containers/243LU875E5.groups.com.apple.podcasts/Library/Cache`. Unlike, say `~/Documents`, this directory is of course not meant to be accessed directly by regular users, but it is there.
Other storage mechanisms than files are referenced in the [offline content](#offline-content-available-and-media-playable) section.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png", alt="The macOS Finder navigated to the Podcasts app's system directory.", width="800", height="337" %}
  <figcaption>Podcast episodes are stored in a special system app folder.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="/file-system-access/">File System Access API</a> enables developers to get access to the local file system
  of the device. You can use it directly or via the <a href="https://github.com/GoogleChromeLabs/browser-fs-access">browser-fs-access</a>
  support library that transparently provides a fallback for browsers that do not support the API.
  For security reasons, system directories are not web-accessible.
{% endDetails %}

## Platform look and feel

There is a more subtle thing that is self-evident for an iOS application like Podcasts: none of the text labels are selectable and all text blends in with the system font of the machine. Also my choice of system color theme (dark mode) is respected.

<div class="switcher">
  <figure>
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png", alt="The Podcasts app in dark mode.", width="800", height="463" %}
    <figcaption>The Podcasts app supports light and dark mode.</figcaption>
  </figure>
  <figure>
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png", alt="The Podcasts app in light mode.", width="800", height="463" %}
    <figcaption>The app uses the default system font.</figcaption>
  </figure>
</div>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  By leveraging the <a href="https://developer.mozilla.org/docs/Web/CSS/user-select"><code>user-select</code></a>
  CSS property with the value of <a href="https://developer.mozilla.org/docs/Web/CSS/user-select#Syntax:~:text=none,-The"><code>none</code></a>,
  you can protect UI elements from being accidentally selected.
  Be sure, though, to not abuse this property for making <em>app contents</em> unselectable.
  It should only be used for UI elements like button texts, etc.
  The <a href="https://developer.mozilla.org/docs/Web/CSS/font-family#<generic-name>:~:text=system%2Dui,-Glyphs"><code>system-ui</code></a>
  value for the <a href="https://developer.mozilla.org/docs/Web/CSS/font-family"><code>font-family</code></a> CSS property allows you to
  specify the default UI font of the system to be used for your app.
  Finally, your app can obey to the user's color scheme preference by respecting their <a href="/prefers-color-scheme/"><code>prefers-color-scheme</code></a> choice, with an optional <a href="https://github.com/GoogleChromeLabs/dark-mode-toggle">dark mode toggle</a>
  to override it.
  Another thing to decide on might be what the browser should do when reaching
  the boundary of a scrolling area, for example, to implement custom <em>pull to refresh</em>.
  This is possible with the <a href="https://developer.mozilla.org/docs/Web/CSS/overscroll-behavior"><code>overscroll-behavior</code></a> CSS property.
{% endDetails %}

## Customized title bar

When you look at the Podcasts app window, you notice that it does not have a classic integrated title bar and toolbar, like, for example, the Safari browser window, but a customized experience that looks like a sidebar docked to the main player window.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png", alt="The Safari browser's integrated tile bar and toolbar.", width="800", height="40" %}
  <figcaption></figcaption>
</figure>

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png", alt="The Podcasts app's customized split customized title bar.", width="800", height="43" %}
  <figcaption>Customized title bars of Safari and Podcasts.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  While not currently possible, <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/TitleBarCustomization/explainer.md">title bar customization</a> is being worked on at the moment.
  You can (and should), however, specify the <a href="/add-manifest/#display"><code>display</code></a> and the
  <a href="/add-manifest/#theme-color"><code>theme-color</code></a> properties of the web app manifest to
  determine the look and feel of your application window and to decide which default browser controls—potentially none of them—should be shown.
{% endDetails %}

## Snappy animations

In-app animations are snappy and smooth in Podcasts. For example, when I open the **Episode Notes** drawer on the right, it elegantly slides in. When I remove one episode from my downloads, the remaining episodes float up and consume the screen real estate that was freed by the deleted episode.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png", alt="The Podcasts app with the 'Episode Notes' drawer expanded.", width="800", height="463" %}
  <figcaption>In-app animations like when opening a drawer are snappy.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  Performant animations on the web are certainly possible if you take into account a number of best practices
  outlined in the article <a href="https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance">Animations and Performance</a>.
  Scroll animations as commonly seen in paginated content or media carousels can be massively improved by using the <a href="https://developers.google.com/web/updates/2018/07/css-scroll-snap">CSS Scroll Snap</a> feature.
  For full control, you can use the <a href="https://developer.mozilla.org/docs/Web/API/Web_Animations_API">Web Animations API</a>.
{% endDetails %}

## Content surfaced outside of app

The Podcasts app on iOS can surface content in other locations than the actual application, for example, in the system's Widgets view, or in the form of a Siri Suggestion. Having proactive, usage-based calls-to-action that just require a tap to interact with can greatly increase the re-engagement rate of an app like Podcasts.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png", alt="iOS Widget view showing the Podcasts app suggesting a new episode of a podcast.", width="751", height="1511" %}
  <figcaption>App content is surfaced outside of the main Podcasts app.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="/content-indexing-api/">Content Index API</a> allows your application
  to tell the browser which content of the PWA is available offline.
  This allows the browser to surface this content outside of the main app.
  By marking up interesting content in your app as suitable for <a href="https://developers.google.com/search/docs/data-types/speakable">speakable</a>
  audio playback and by using <a href="https://developers.google.com/search/docs/guides/search-gallery">structured markup</a> in general,
  you can help search engines and virtual assistants like the Google Assistant present your offerings in an ideal light.
{% endDetails %}

## Lock screen media control widget

When a podcast episode is playing, the Podcasts app shows a beautiful control widget on the lock screen that features metadata like the episode artwork, the episode title, and the podcast name.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png", alt="iOS media playback widget on the lock screen showing a podcast episode with rich metadata.", width="751", height="1511" %}
  <figcaption>Media playing in the app can be controlled from the lock screen.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="/media-session/">Media Session API</a> lets you specify metadata like artwork, track titles, etc.
  that then gets displayed on the lock screen, smartwatches, or other media widgets in the browser.
{% endDetails %}

## Push notifications

Push notifications have become a bit of an annoyance on the web
(albeit [notification prompts are a lot quieter](https://blog.chromium.org/2020/01/introducing-quieter-permission-ui-for.html) now).
But if used properly, they can add a lot of value.
For example, the iOS Podcasts app can optionally notify me of new episodes of podcasts I am subscribed to or recommend new ones, as well as alert me of new app features.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png", alt="iOS Podcasts app in the 'Notifications' settings screen showing the 'New Episodes' notifications toggle activated.", width="751", height="1511" %}
  <figcaption>Apps can send push notifications to inform the user about new content.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="https://developers.google.com/web/fundamentals/push-notifications">Push API</a>
  allows your app to receive push notifications so you can notify your users about noteworthy events around your PWA.
  For notifications that should fire at a known time in the future and that do not require a network connection,
  you can use the <a href="/notification-triggers/">Notification Triggers API</a>.
{% endDetails %}

## App icon badging

Whenever there are new episodes available for one of the podcasts I am subscribed to, an app icon badge on the Podcasts home screen icon appears, again encouraging me to re-engage with the app in a way that is not intrusive.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png", alt="iOS settings screen showing the 'Badges' toggle activated.", width="751", height="1511" %}
  <figcaption>Badges are a subtle way for applications to inform users about new content.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  You can set app icon badges with the <a href="/badging-api/">Badging API</a>.
  This is especially useful when your PWA has some notion of "unread" items or when you need a means
  to unobtrusively draw the user's attention back to the app.
{% endDetails %}

## Media playback takes precedence over energy saver settings

When podcast media is playing, the screen may turn off, but the system will not enter standby mode.
Apps can optionally keep the screen awake, too, for example to display lyrics or captions.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png", alt="macOS Preferences in the 'Energy Saver' section.", width="800", height="573" %}
  <figcaption>Apps can keep the screen awake.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  The <a href="/wakelock/">Screen Wake Lock API</a> allows you to prevent the screen from turning off.
  Media playback on the web automatically prevents the system from entering standby mode.
{% endDetails %}

## App discovery through an app store

While the Podcasts app is part of the macOS desktop experience, on iOS it needs to be installed from the App Store.
A quick search for `podcast`, `podcasts`, or `apple podcasts` immediately turns the app up in the App Store.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png", alt="iOS App Store search for 'podcasts' reveals the Podcasts app.", width="751", height="1511" %}
  <figcaption>Users have learned to discover apps in app stores.</figcaption>
</figure>

{% Details %}
{% DetailsSummary %}
  How to do this on the web
{% endDetailsSummary %}
  While Apple does not allow PWAs on the App Store, on Android, you can submit your PWA
  <a href="/using-a-pwa-in-your-android-app/">wrapped in a Trusted Web Activity</a>.
  The <a href="https://github.com/GoogleChromeLabs/bubblewrap"><code>bubblewrap</code></a> script makes this a painless operation.
  This script is also what internally powers <a href="https://www.pwabuilder.com/">PWABuilder</a>'s Android app export feature,
  which you can use without touching the command line.
{% endDetails %}

## Feature summary

The table below shows a compact overview of all features and provides a list of useful resources for realizing them on the web.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Feature</th>
        <th>Useful resources for doing this on the web</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><a href="#capable-of-running-offline">Capable of running offline</a></td>
        <td>
          <ul>
            <li>
              <a href="https://developers.google.com/web/fundamentals/architecture/app-shell"
                >App shell model</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#offline-content-available-and-media-playable">Offline content available and media playable</a></td>
        <td>
          <ul>
            <li>
              <a
                href="https://developer.chrome.com/docs/workbox/serving-cached-audio-and-video/"
                >Serve cached audio and video</a
              >
            </li>
            <li><a href="https://developer.chrome.com/docs/workbox/">Workbox library</a></li>
            <li><a href="/storage-for-the-web/">Storage API</a></li>
            <li><a href="/persistent-storage/">Persistent Storage API</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#proactive-background-downloading">Proactive background downloading</a></td>
        <td>
          <ul>
            <li>
              <a href="https://developers.google.com/web/updates/2018/12/background-fetch"
                >Background Fetch API</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#sharing-to-and-interacting-with-other-applications">Sharing to and interacting with other applications</a></td>
        <td>
          <ul>
            <li><a href="/web-share/">Web Share API</a></li>
            <li><a href="/web-share-target/">Web Share Target API</a></li>
            <li>
              <a href="/image-support-for-async-clipboard/">Async Clipboard API</a>
            </li>
            <li><a href="/contact-picker/">Contact Picker API</a></li>
            <li>
              <a href="/get-installed-related-apps/"
                >Get Installed Related Apps API</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#background-app-refreshing">Background app refreshing</a></td>
        <td>
          <ul>
            <li>
              <a href="/periodic-background-sync/">Periodic Background Sync API</a>
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#state-synchronized-over-the-cloud">State synchronized over the cloud</a></td>
        <td>
          <ul>
            <li>
              <a href="https://developers.google.com/web/updates/2015/12/background-sync"
                >Background Sync API</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#hardware-media-key-controls">Hardware media key controls</a></td>
        <td>
          <ul>
            <li><a href="/media-session/">Media Session API</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#multitasking-and-app-shortcut">Multitasking and app shortcut</a></td>
        <td>
          <ul>
            <li><a href="/install-criteria/">Installability criteria</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#quick-actions-in-context-menu">Quick actions in context menu</a></td>
        <td>
          <ul>
            <li><a href="/app-shortcuts/">App icon shortcuts</a></li>
            <li>
              <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/master/RunOnLogin"
                >Run on login</a
              > (early stage)
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#act-as-default-app">Act as default app</a></td>
        <td>
          <ul>
            <li>
              <a
                href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/URLProtocolHandler/explainer.md"
                >URL protocol handling</a
              > (early stage)
            </li>
            <li>
              <a
                href="https://developer.mozilla.org/docs/Web/API/Navigator/registerProtocolHandler"
                ><code>registerProtocolHandler()</code></a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#local-file-system-integration">Local file system integration</a></td>
        <td>
          <ul>
            <li><a href="/file-system-access/">File System Access API</a></li>
            <li>
              <a href="https://github.com/GoogleChromeLabs/browser-fs-access"
                >browser-fs-access library</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#platform-look-and-feel">Platform look and feel</a></td>
        <td>
          <ul>
            <li>
              <a
                href="https://developer.mozilla.org/docs/Web/CSS/user-select#Syntax:~:text=none,-The"
                ><code>user-select: none</code></a
              >
            </li>
            <li>
              <a href="https://developer.mozilla.org/docs/Web/CSS/font-family"
                ><code>font-family: system-ui</code></a
              >
            </li>
            <li>
              <a href="/prefers-color-scheme/"><code>prefers-color-scheme</code></a>
            </li>
            <li>
              <a href="https://github.com/GoogleChromeLabs/dark-mode-toggle">Dark mode toggle</a>
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#customized-title-bar">Customized title bar</a></td>
        <td>
          <ul>
            <li>
              <a
                href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/TitleBarCustomization/explainer.md"
                >Title bar customization</a
              > (early stage)
            </li>
            <li><a href="/add-manifest/#display">Display mode</a></li>
            <li><a href="/add-manifest/#theme-color">Theme color</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#snappy-animations">Snappy animations</a></td>
        <td>
          <ul>
            <li>
              <a
                href="https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance"
                >Animations and performance tips</a
              >
            </li>
            <li>
              <a href="https://developers.google.com/web/updates/2018/07/css-scroll-snap"
                >CSS Scroll Snap</a
              >
            </li>
            <li>
              <a href="https://developer.mozilla.org/docs/Web/API/Web_Animations_API"
                >Web Animations API</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#content-surfaced-outside-of-app">Content surfaced outside of app</a></td>
        <td>
          <ul>
            <li><a href="/content-indexing-api/">Content Index API</a></li>
            <li>
              <a href="https://developers.google.com/search/docs/data-types/speakable"
                >Speakable content</a
              >
            </li>
            <li>
              <a href="https://developers.google.com/search/docs/guides/search-gallery"
                >Structured markup</a
              >
            </li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#lock-screen-media-control-widget">Lock screen media control widget</a></td>
        <td>
          <ul>
            <li><a href="/media-session/">Media Session API</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#push-notifications">Push notifications</a></td>
        <td>
          <ul>
            <li>
              <a href="https://developers.google.com/web/fundamentals/push-notifications"
                >Push API</a
              >
            </li>
            <li><a href="/notification-triggers/">Notification Triggers API</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#app-icon-badging">App icon badging</a></td>
        <td>
          <ul>
            <li><a href="/badging-api/">Badging API</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#media-playback-takes-precedence-over-energy-saver-settings">Media playback trumps energy saver settings</a></td>
        <td>
          <ul>
            <li><a href="/wakelock/">Screen Wake Lock API</a></li>
          </ul>
        </td>
      </tr>
      <tr>
        <td><a href="#app-discovery-through-an-app-store">App discovery through an app store</a></td>
        <td>
          <ul>
            <li>
              <a href="/using-a-pwa-in-your-android-app/">Trusted Web Activity</a>
            </li>
            <li>
              <a href="https://github.com/GoogleChromeLabs/bubblewrap"
                ><code>bubblewrap</code> library</a
              >
            </li>
            <li><a href="https://www.pwabuilder.com/">PWABuilder tool</a></li>
          </ul>
        </td>
      </tr>
    </tbody>
  </table>
</div>

## Conclusion

PWAs have come a long way since their introduction in 2015.
In the context of [Project Fugu 🐡](https://developer.chrome.com/blog/fugu-status), the cross-company Chromium team is working on closing the last remaining gaps.
By following even only some of the pieces of advice in this article,
you can piece by piece get closer to that app-like feeling and make your users forget
that they are dealing with "just a website", because, honestly, most of them do not care
how your app is built (and why should they?), as long as it feels like a *real* app.

## Acknowledgements

This article was reviewed by
[Kayce Basques](/authors/kaycebasques/),
[Joe Medley](/authors/joemedley/),
[Joshua Bell](https://github.com/inexorabletash),
[Dion Almaer](https://blog.almaer.com/),
[Ade Oshineye](https://blog.oshineye.com/),
[Pete LePage](/authors/petelepage/),
[Sam Thorogood](/authors/samthor/),
[Reilly Grant](https://github.com/reillyeon),
and [Jeffrey Yasskin](https://github.com/jyasskin).

# Make it installable

This glitch contains the [web manifest](https://glitch.com/edit/#!/make-it-installable?path=manifest.json) with the required fields to make a Web App [installable](/install-criteria/).

It also has an install button that is hidden by default.

## Listen for the beforeinstallprompt event

When the browser fires the `beforeinstallprompt` event, that's the indication
that the Web App can be installed and an install button can be shown
to the user. The `beforeinstallprompt` event is fired when the app meets [the
installability criteria](/install-criteria/).

Capturing the event enables developers to customize the installation and prompt the user
to install when they consider it is the best time.

{% Instruction 'remix', 'ol' %}
1. Add a `beforeinstallprompt` event handler to the `window` object.
2. Save the `event` as a global variable; we'll need it later to show the
    prompt.
3. Unhide the install button.

Code:

```js
window.addEventListener('beforeinstallprompt', (event) => {
  // Prevent the mini-infobar from appearing on mobile.
  event.preventDefault();
  console.log('👍', 'beforeinstallprompt', event);
  // Stash the event so it can be triggered later.
  window.deferredPrompt = event;
  // Remove the 'hidden' class from the install button container.
  divInstall.classList.toggle('hidden', false);
});
```

## Handle the install button click

To show the install prompt, call `prompt()` on the saved `beforeinstallprompt`
event. Calling `prompt()` is done in the install button click handler because
`prompt()` must be called from a user gesture.

{% Aside 'gotcha' %}
You need to open your app in its own browser tab. The side-by-side preview pane
in Glitch doesn't work for this codelab, since it will be run in an iframe.
Choose Glitch's "Preview in a new window" mode instead.
{% endAside %}

1. Add a click event handler for the install button.
1. Call `prompt()` on the saved `beforeinstallprompt` event.
1. Log the results of the prompt.
1. Set the saved `beforeinstallprompt` event to null.
1. Hide the install button.

Code:

```js
butInstall.addEventListener('click', async () => {
  console.log('👍', 'butInstall-clicked');
  const promptEvent = window.deferredPrompt;
  if (!promptEvent) {
    // The deferred prompt isn't available.
    return;
  }
  // Show the install prompt.
  promptEvent.prompt();
  // Log the result
  const result = await promptEvent.userChoice;
  console.log('👍', 'userChoice', result);
  // Reset the deferred prompt variable, since
  // prompt() can only be called once.
  window.deferredPrompt = null;
  // Hide the install button.
  divInstall.classList.toggle('hidden', true);
});
```

## Track the install event

Installing a Web App through an install button is only one way users
can install it. They can also use Chrome's menu, the mini-infobar, and
through [an icon in the omnibox](/promote-install/#browser-promotion). You can
track all of these ways of installation by listening for the `appinstalled`
event.

1. Add an `appinstalled` event handler to the `window` object.
2. Log the install event to analytics or other mechanism.

Code:

```js
window.addEventListener('appinstalled', (event) => {
  console.log('👍', 'appinstalled', event);
  // Clear the deferredPrompt so it can be garbage collected
  window.deferredPrompt = null;
});
```

## Further reading

Congratulations, you app is now installable!

Here are some additional things that you can do:

+  [Detect if your app is launched from the home screen](/customize-install/#detect-mode)
+  [Show the operating system's app install prompt instead](https://developer.chrome.com/blog/app-install-banners-native/)

# How to provide your own in-app install experience

Many browsers make it possible for you to enable and promote the installation
of your Progressive Web App (PWA) directly within the user interface of your
PWA. Installation (sometimes formerly referred to as Add to Home Screen),
makes it easy for users to install your PWA on their mobile or desktop device.
Installing a PWA adds it to a user's launcher, allowing it to be run like any
other installed app.

In addition to the [browser provided install experience](/promote-install/#browser-promotion),
it's possible to provide your own custom install flow, directly within your app.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SW3unIBfyMRTZNK0DRIw.png", alt="Install App button provided in the Spotify PWA", width="491", height="550" %}
  <figcaption>
    "Install App" button provided in the Spotify PWA
  </figcaption>
</figure>

When considering whether to promote install, it's best to think about how
users typically use your PWA.  For example, if there's a set of users who
use your PWA multiple times in a week, these users might benefit from the
added convenience of launching your app from a smartphone homescreen or
from the Start menu in a desktop operating system.  Some productivity and
entertainment applications also benefit from the extra screen real-estate
created by removing the browser toolbars from the window in installed
`standalone` or `minimal-ui` modes.

## Promoting installation {: #promote-installation }

To indicate your Progressive Web App is installable, and to provide a custom
in-app install flow:

1. Listen for the `beforeinstallprompt` event.
2. Save the `beforeinstallprompt` event, so it can be used to trigger the
   install flow later.
3. Alert the user that your PWA is installable, and provide a button or other
   element to start the in-app installation flow.

{% Aside %}
  The `beforeinstallprompt` event, and the `appinstalled` event have been moved
  from the web app manifest spec to their own
  [incubator](https://github.com/WICG/beforeinstallprompt). The Chrome team
  remains committed to supporting them, and has no plans to remove or deprecate
  support. Google's Web DevRel team continues to recommend using them to provide a customized
  install experience.
{% endAside %}

### Listen for the `beforeinstallprompt` event {: #beforeinstallprompt }

If your Progressive Web App meets the required [installation criteria](/install-criteria/),
the browser fires a `beforeinstallprompt` event. Save a reference to the
event, and update your user interface to indicate that the user can install
your PWA. This is highlighted below.

```js
// Initialize deferredPrompt for use later to show browser install prompt.
let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent the mini-infobar from appearing on mobile
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;
  // Update UI notify the user they can install the PWA
  showInstallPromotion();
  // Optionally, send analytics event that PWA install promo was shown.
  console.log(`'beforeinstallprompt' event was fired.`);
});
```

{% Aside %}
There are many different [patterns](/promote-install/) that you can use to
notify the user your app can be installed and provide an in-app install
flow, for example, a button in the header, an item in the navigation menu,
or an item in your content feed.
{% endAside %}

### In-app installation flow {: #in-app-flow }

To provide in-app installation, provide a button or other interface element
that a user can click to install your app. When the element is
clicked, call `prompt()` on the saved `beforeinstallprompt` event (stored
in the `deferredPrompt` variable). It shows the user a modal install dialog,
asking them to confirm they want to install your PWA.

```js
buttonInstall.addEventListener('click', async () => {
  // Hide the app provided install promotion
  hideInstallPromotion();
  // Show the install prompt
  deferredPrompt.prompt();
  // Wait for the user to respond to the prompt
  const { outcome } = await deferredPrompt.userChoice;
  // Optionally, send analytics event with outcome of user choice
  console.log(`User response to the install prompt: ${outcome}`);
  // We've used the prompt, and can't use it again, throw it away
  deferredPrompt = null;
});
```

The `userChoice` property is a promise that resolves with the user's choice.
You can only call `prompt()` on the deferred event once. If the user
dismisses it, you'll need to wait until the `beforeinstallprompt` event
is fired again, typically immediately after the `userChoice` property
has resolved.

{% Aside 'codelab' %}
[Make a site installable using the beforeinstallprompt event](/codelab-make-installable).
{% endAside %}

## Detect when the PWA was successfully installed {: #detect-install }

You can use the `userChoice` property to determine if the user installed
your app from within your user interface. But, if the user installs your
PWA from the address bar or other browser component, `userChoice` won't
help. Instead, you should listen for the `appinstalled` event. It is fired
whenever your PWA is installed, no matter what mechanism is used to install
your PWA.

```js
window.addEventListener('appinstalled', () => {
  // Hide the app-provided install promotion
  hideInstallPromotion();
  // Clear the deferredPrompt so it can be garbage collected
  deferredPrompt = null;
  // Optionally, send analytics event to indicate successful install
  console.log('PWA was installed');
});
```

## Detect how the PWA was launched {: #detect-launch-type }

The CSS `display-mode` media query indicates how the PWA was launched,
either in a browser tab, or as an installed PWA. This makes it possible to
apply different styles depending on how the app was launched. For example,
always hide the install button and provide a back button when launched as an
installed PWA.

### Track how the PWA was launched

To track how users launch your PWA, use `matchMedia()` to test the
`display-mode` media query. Safari on iOS doesn't support
this yet, so you must check `navigator.standalone`, it returns a boolean
indicating whether the browser is running in standalone mode.

```js
function getPWADisplayMode() {
  const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
  if (document.referrer.startsWith('android-app://')) {
    return 'twa';
  } else if (navigator.standalone || isStandalone) {
    return 'standalone';
  }
  return 'browser';
}
```

### Track when the display mode changes

To track if the user changes between `standalone`, and `browser tab`, listen for
changes to the `display-mode` media query.

```js
window.matchMedia('(display-mode: standalone)').addEventListener('change', (evt) => {
  let displayMode = 'browser';
  if (evt.matches) {
    displayMode = 'standalone';
  }
  // Log display mode change to analytics
  console.log('DISPLAY_MODE_CHANGED', displayMode);
});
```

### Update UI based on the current display mode

To apply a different background color for a PWA when launched as an installed
PWA, use conditional CSS:

```css
@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}
```

## Updating your app's icon and name

What if you need to update your app name, or provide new icons?
Check out [How Chrome handles updates to the web app manifest](/manifest-updates/)
to see when and how are those changes are reflected in Chrome.

# How to define your install strategy

In the past, app installation was only possible in the context of platform-specific applications. Today, modern web apps offer installable experiences that provide the same level of integration and reliability as platform-specific apps.

You can achieve this in different ways:

- Installing the PWA [from the browser](/customize-install/).
- Installing the PWA [from the app store](https://developer.chrome.com/docs/android/trusted-web-activity/).

Having different distribution channels is a powerful way of reaching a broad number of users, but choosing the right strategy to promote your the installation of your PWA can be challenging.

This guide explores best practices for combining different installation options to increase installation rates and avoid platform competition and cannibalization. The installation offerings covered include PWAs installed from both the browser and the App Store, as well as platform-specific apps.

{% YouTube '6R9pupbDXYw' %}

## Why make your web app installable?

Installed Progressive Web Apps run in a standalone window instead of a browser tab. They're launchable from the user's home screen, dock, taskbar, or shelf. It's possible to search for them on a device and jump between them with the app switcher, making them feel like part of the device they're installed on.

But having both an installable web app and a platform-specific app can be confusing for users. For some users platform-specific apps may be the best choice, but for others they can present some drawbacks:

- **Storage constraints:** installing a new app might mean deleting others, or cleaning up space, by removing valuable content. This is especially disadvantageous for users on low-end devices.
- **Available bandwidth:** downloading an app can be a costly and slow process, even more so for users on slow connections and expensive data plans.
- **Friction:** leaving a website and moving to a store to download an app creates additional friction and delays a user action that could be performed directly in the web.
- **Update cycle:** making changes in platform-specific apps might require going through an app review process, which can slow down changes and experiments (A/B tests, for example).

In some cases, the percentage of users who won't download your platform-specific app might be large, for example: those that think that they won't use the app very often, or can't justify spending several megabytes of storage or data. You can determine the size of this segment in several ways, for example by using an analytics setup to track the percentage of "mobile web only" users.

If the size of this segment is considerable, that's a good indication that you need to provide alternative ways of installing your experiences.

## Promoting installation of your PWA through the browser

If you have a high quality PWA, it may be better to promote its installation over your platform-specific app. For example, if the platform-specific app is missing functionality offered by your PWA, or if it hasn't been updated in a while. It can also be helpful to promote installation of your PWA if the platform-specific app wasn't optimized for bigger screens, such as on ChromeOS.

For some apps, driving platform-specific app installations is a key part of the business model, in that case, it makes business sense to promote installation of your platform-specific app. But, some users might be more comfortable staying on the web. If that segment can be identified, the PWA prompt can be shown only to them (what we call "PWA as fallback").

### PWA as primary installable experience

Once a PWA meets the [installability criteria](/install-criteria/), most browsers show an indication that the PWA is installable. For example, desktop Chrome shows an installable icon in the address bar, and on mobile, it shows a mini-infobar:

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1VOvbQjeenZOBAmzjVN5.png", alt="Standard Chrome install prompt, called a mini-infobar", width="400", height="208" %}
  <figcaption>
    The mini-infobar.
  </figcaption>
</figure>

While that may be enough for some experiences, if your goal is to drive installations of your PWA, we highly recommend you listen for the [`BeforeInstallPromptEvent`](https://developer.mozilla.org/docs/Web/API/BeforeInstallPromptEvent), and follow the [patterns for promoting the installation](/promote-install/) of your PWA.


## Prevent your PWA from cannibalizing your platform-specific app install rate

In some cases, you may choose to promote the installation of your platform-specific app over your PWA, but in this case, we still recommend you provide a mechanism to allow users to install your PWA. This fallback option makes it possible for users who can't, or don't want to install your platform-specific app to get a similar, installed experience.

The first step to implement this strategy is to define a heuristic for when you'll show the user an install promotion for your PWA.

**For example:** A PWA user is a user that has seen the platform-specific app install prompt and not installed the platform-specific app. They have returned to the site at least five times, or they have clicked the app banner, but have continued using the website instead.

Then, the heuristic can be implemented in the following way:

1. Show the platform-specific app install banner.
1. If a user dismisses the banner, set a cookie with that information (e.g. `document.cookie = "app-install-banner=dismissed"`).
1. Use another cookie to track the number of user visits to the site (e.g. `document.cookie = "user-visits=1"`).
1. Write a function, such as `isPWAUser()`, that uses the information previously stored in the cookies along with the [`getInstalledRelatedApps()`](/get-installed-related-apps/) API to determine if a user is considered a "PWA user".
1. When the user performs a meaningful action, call `isPWAUser()`. If the function returns true and the PWA install prompt was saved previously, you can show the PWA install button.

## Promoting installation of your PWA through an app store

Apps for App stores can be built with different technologies, including PWA techniques. In [Blending PWA into native environments](https://youtu.be/V7YX4cZ_Cto) you can find a summary of the technologies that can be used to that end.

In this section we'll classify apps in the store in two groups:

- **Platform-specific apps:** these apps are mostly built with platform-specific code. Their sizes depend on the platform, but they're usually above 10MB on Android, and 30MB on iOS. You might want to promote your platform-specific app if you don't have a PWA, or if the platform-specific app presents a more complete feature set.
- **Lightweight apps:** these apps can be built with platform-specific code as well, but they are commonly built with web technology, packaged in a platform-specific wrapper. Full PWAs can be uploaded to the stores as well. (This is discussed [later](#promoting-lightweight-apps) in this article.) Some companies opt to provide these as "lite" experiences, and others have used this approach for their flagship (core) apps as well.

### Promoting Lightweight Apps

According to a [Google Play study](https://medium.com/googleplaydev/shrinking-apks-growing-installs-5d3fcba23ce2), for every 6 MB increase to an APK's size, the install conversion rate decreases by 1%. This means that the download completion rate of an app of 10 MB could be approximately **30% higher than an app of 100 MB!**

To address this, some companies are leveraging their PWA to provide a lightweight version of their app in the Play Store using [Trusted Web Activities](https://developer.chrome.com/docs/android/trusted-web-activity/) (TWA). TWAs wrap your PWA in a webview like component, and the resulting app size is usually only a few megabytes.

Oyo, one of India's largest hospitality companies, built a [Lite version of their app](/oyo-lite-twa/), and made it available in the Play Store using a TWA. At the time this article was written, the Oyo app was only 850 KB, just 7% the size of their Android app. And once installed, it's indistinguishable from their Android app:

<figure>
  <video controls autoplay loop muted>
    <source src="https://storage.googleapis.com/web-dev-assets/oyo-case-study/oyo-lite.webm" type="video/webm; codecs=vp8">
    <source src="https://storage.googleapis.com/web-dev-assets/oyo-case-study/oyo-lite.mp4" type="video/mp4; codecs=h264">
  </video>
 <figcaption>
    OYO Lite in action.
  </figcaption>
</figure>

Oyo kept both the flagship and "lite" app versions in the store, providing a choice to their users.

#### Providing a lightweight web experience

Intuitively, users on low-end devices, might be more inclined to download lightweight versions of apps than users on high-end phones. Therefore, if it's possible to identify a user's device, you could prioritize the lightweight app install banner over the heavier platform-specific app version.

On the web, it's possible to obtain device signals and approximately map them to device categories (e.g. "high", "mid", or "low"). You can obtain this information in different ways, using either JavaScript APIs or client hints.

#### Using JavaScript

Using JavaScript properties such as [navigator.hardwareConcurrency](https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency), [navigator.deviceMemory](https://developer.mozilla.org/docs/Web/API/Navigator/deviceMemory), and [navigator.connection](https://developer.mozilla.org/docs/Web/API/Navigator/connection) you can get information about the device CPU, memory and network status respectively. For example:

```javascript
const deviceCategory = req.get('Device-Memory') < 1 ? 'lite' : 'full';`
```

#### Using client hints

Device signals can be also inferred in HTTP request headers, through [client hints](https://developer.mozilla.org/docs/Glossary/Client_hints). Here's how you can implement the previous code for device memory with client hints:

First, tell the browser you are interested in receiving device memory hints in the header of the HTTP response for any first-party request:

```html
HTTP/1.1 200 OK
Content-Type: text/html
Accept-CH: Device-Memory
```

Then, you'll start receiving `Device-Memory` information in the request header of HTTP requests:

```html
GET /main.js HTTP/1.1
Device-Memory: 0.5
```

You can use this information in your backends to store a cookie with the category of the user's device:

```javascript
app.get('/route', (req, res) => {
  // Determine device category

 const deviceCategory = req.get('Device-Memory') < 1 ? 'lite' : 'full';

  // Set cookie
  res.setCookie('Device-Category', deviceCategory);
  …
});
```

Finally, create your own logic to map this information to device categories, and show the corresponding app install prompt on each case:

```javascript
if (isDeviceMidOrLowEnd()) {
   // show "Lite app" install banner or PWA A2HS prompt
} else {
  // show "Core app" install banner
}
```

{% Aside %}
Covering in depth techniques on how to map device signals to device categories is out of the scope of this guide, but you can check out Addy Osmani's [adaptive loading guide](https://dev.to/addyosmani/adaptive-loading-improving-web-performance-on-low-end-devices-1m69), Philip Walton's [The Device Memory API](https://developer.chrome.com/blog/device-memory/)  and Jeremy Wagner's [Adapting to Users with Client Hints](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints/) for more information on best practices around this.
{% endAside %}

## Conclusion

The ability to have an icon in the user's home screen is one of the most engaging features of applications. Given that historically this was only possible for apps installed from app stores, companies might think that showing an app store install banner would be enough to convince users to install their experiences. Currently there are more options for letting users install an app, including offering lightweight app experiences in the stores, and letting users add PWAs to the home screen by prompting them to do it directly from the website.

# How Progressive Web Apps can drive business success

Progressive Web Apps are on a lot of companies' roadmap to modernize their
websites and adapt to users' new expectations. As with many technologies, PWAs
raise questions: is it what my customers want, how much will it grow my
business, what is technically feasible?

<figure>
  {% Img src="image/admin/o70RxMcAQVPrjxH34a8r.jpg", alt="Identify your stakeholders.", width="800", height="254" %}
  <figcaption>
    Identify your stakeholders.
  </figcaption>
</figure>

To shape your digital strategy, several stakeholders are often involved: the
product manager and chief marketing officer are co-owners of the business impact of each feature,
the chief technology officer assesses the feasibility and reliability of a technology, the user experience
researchers validate that a feature answers a real customer issue.

This article aims to help you answer those three questions and shape your
PWA project. You will start from your customer needs, translate this into
PWA features, and focus on measuring the business impact of each.

## PWAs solve customer needs {: #solve-customer-needs }

One rule we love to follow at Google when making products is
"[focus on the user and all else will follow][ten-things-we-know]". Think
_user-first_: what are my customers' needs, and how does a PWA provide them?

<figure>
  {% Img src="image/admin/TcmXmWb5mSUqal98NIAH.jpg", alt="Identify customer needs.", width="800", height="262" %}
  <figcaption>
    Identify customer needs.
  </figcaption>
</figure>

When doing user research, we find some interesting patterns:

* Users hate delays and unreliability on mobile. The level of stress caused by
  mobile delays is [comparable to watching a horror movie][horror-movie].
* Fifty percent of smartphone users are more likely to use a company's mobile
  site when browsing or shopping because they
  [don't want to download an app][twg-shopping].
* One of the top reasons for uninstalling an app is
  [limited storage][twg-uninstall]. (An installed PWA usually takes
  less than 1MB).
* Smartphone users are more likely to purchase from mobile sites that
  [offer relevant recommendations][twg-purchase] on products, and 85% of
  smartphone users say [mobile notifications are useful][twg-notifications].

According to those observations, we found out that customers prefer
experiences that are fast, installable, reliable, and engaging (F.I.R.E.).

## PWAs leverage modern web capabilities {: #modern-capabilities }

PWAs provide a set of best practices and modern web APIs that are aimed at
meeting your customers' needs by making your site fast, installable, reliable,
and engaging.

For example, using a service worker to [cache your resources][cache-resources]
and doing [predictive prefetching][wb-precache] makes your site faster, and more
reliable. Making your site Installable provides an easy way for your customers
to access it directly from their home screen or app launcher. And APIs such as
[Web Push Notifications][mdn-web-push] make it easier to re-engage your users
with personalized content to generate loyalty.

<figure>
  {% Img src="image/admin/rP0eNCflNYOhzjPi1Lq5.jpg", alt="Improving the user experience with web capabilities.", width="800", height="393" %}
  <figcaption>
    Improving the user experience with web capabilities.
  </figcaption>
</figure>

## Understand the business impact {: #business-impact }

The business success definition can be many things depending on your
activity:

* Users spending more time on your service
* Reduced bounce rates for your leads
* Improved conversion rates
* More returning visitors

Most PWA projects result in a higher mobile conversion rate. You can
learn more from the numerous [PWA case studies][pwa-case-studies]. Depending
on your objectives, you may want to prioritize the aspects of PWAs that make
more sense for your business, and it's completely OK. PWA features can be
cherry-picked and launched separately.

Let's measure the business impact of each of these great F.I.R.E features.

### The business impact of a fast website {: #impact-fast }

A recent [study from Deloitte Digital][deloitte-study] shows that page speed
has a significant impact on business metrics.

There's much you can do to optimize the speed of your site to improve
critical user journeys for all of your users. If you don't know where to
start, take a look at our [Fast](/fast/) section, and use
[Lighthouse][lighthouse] to prioritize the most important things to fix.

When working on your speed optimizations, start measuring your site speed
frequently with appropriate tools and metrics to monitor your progress.
For example, measure your metrics with [Lighthouse][lighthouse],
fix clear targets like having ["Good" Core Web Vitals scores](/vitals/#core-web-vitals), and
incorporate a [performance budget into your build process][perf-budget-ci].
Thanks to your daily measurements and [the "value of speed" methodology][value-of-speed],
you can isolate the impact of your incremental speed changes and calculate
how much extra revenue your work has generated.

<figure>
  {% Img src="image/admin/yyRfQaDL3NcGhB0f79RN.jpg", alt="Measure the value of speed and correlate it with conversions.", width="800", height="306" %}
  <figcaption>
    Measure the value of speed and correlate it with conversions.
  </figcaption>
</figure>

Ebay made [speed a company objective][ebay-speed] for 2019. They used
techniques such as performance budget, critical path optimization, and
predictive prefetching. They concluded that for every 100 millisecond
improvement in search page loading time, add-to-card count increased by 0.5%.

<figure>
  {% Img src="image/admin/Qq3wo5UOqzC1ugnTzdqT.jpg", alt="A 100ms improvement in load time resulted in a 0.5% increase in add to cart count for eBay.", width="800", height="184" %}
  <figcaption>
    A 100ms improvement in load time resulted in a 0.5% increase in add to cart count for eBay.
  </figcaption>
</figure>

### The business impact of an installable website {: #impact-installable }

Why would you want a user to install your PWA? To make it easier to come back to
your site. Where an Android app installation would add at least three steps
(redirection to Play Store, downloading the app, launching the app), PWA
installation is done seamlessly in one click without taking the user away from
the current conversion funnel.

<figure>
  {% Img src="image/admin/u1jcKrBBOHEzSz3SqhEB.jpg", alt="The install experience should be seamless.", width="800", height="239" %}
  <figcaption>
    The install experience should be seamless.
  </figcaption>
</figure>

Once an app is installed, users can launch it in one click using the icon on
their home screen, see it in their app tray when they are switching between
apps, or find it via an app search result.  We call this dynamic
Discover-Launch-Switch, and making your PWA installable is the key to
unlocking access.

In addition to being accessible from familiar discovery and launch surfaces
on their device, a PWA launches exactly like a platform-specific app: in a standalone
experience, separate from the browser. Additionally, it benefits from OS-level
device services such as the app switcher and settings.

Users who install your PWA are likely your most engaged users, with better
engagement metrics than casual visitors, including more repeat visits,
longer time on site and higher conversion rates, often at parity with platform-specific
app users on mobile devices.

To make your PWA installable, it needs to meet the
[base criteria](/install-criteria/). Once it meets those criteria, you can
[promote the installation](/promote-install/) within your user experience on
desktop and mobile, including iOS.

<figure>
  {% Img src="image/admin/5sH5YX7kFrwv4f6duqVf.jpg", alt="PWAs are installable everywhere.", width="800", height="227" %}
  <figcaption>
    PWAs are installable everywhere.
  </figcaption>
</figure>

Once you've begun promoting the installation of your PWA, you should
measure how many users are installing your PWA, and how they use your PWA.

To maximize the number of users installing your site, you may want to
[test different][pwabook-ch4] promotion messages ("install in one
second", or "add our shortcut to follow your order" for example), different
placements (header banner, in-feed), and try proposing it at different steps
of the installation funnel (on the second page visited, or after a booking).

To understand where your users are leaving and how to improve retention, the
installation funnel can be [measured][pwabook-ch8] in four ways:

* Number of users eligible to install
* Number of users who clicked on the install prompt
* Number of users who both accepted and declined the install prompt
* Number of users who successfully installed

You can promote your PWA installation to all your users or take a more
cautious approach by experimenting with a small group of users.

A few days or weeks should give you enough data to measure the
impact on your business. What is the behavior of people coming from the
installed shortcut? Do they engage more, do they convert more?

To segment users who installed your PWA, track the [`appinstalled`
event][appinstalled-event], and use JavaScript to [check if users are in
standalone mode][is-standalone], which indicates use of the installed PWA.
Then use these as variables or dimensions for your analytics tracking.

<figure>
  {% Img src="image/admin/H2U4jKTmATNzVJQ3WNCO.jpg", alt="Measure the value of installation.", width="800", height="253" %}
  <figcaption>
    Measure the value of installation.
  </figcaption>
</figure>

The [case study of Weekendesk][twg-weekendest] is interesting; to increase
the chance a user would install their PWA, they promote installation on the
second page visited. Users who launched the PWA from their home screen
were more than twice as likely to book a stay through their PWA!

<figure>
  {% Img src="image/admin/eR23C2o1adHq5tATNw34.jpg", alt="Installed users had a 2.5 times higher conversion rate.", width="800", height="201" %}
  <figcaption>
    Installed users had a 2.5 times higher conversion rate.
  </figcaption>
</figure>

Installation is a great way to encourage people return to your site and improve
your customer loyalty. You can also think of personalizing the experience for
premium users.

Even if you have a platform-specific app, users may feel they don't want to, or
don't need to install it. These semi-engaged users may feel a PWA is right for
them as it is often perceived as lighter, and can be installed with less friction.

<figure>
  {% Img src="image/admin/iNQalNPhjdBueuqPHiad.jpg", alt="PWAs can reach semi-engaged users.", width="800", height="229" %}
  <figcaption>
    PWAs can reach semi-engaged users.
  </figcaption>
</figure>

### The business impact of a reliable website {: #impact-reliable }

The Chrome Dino game, offered when a user is offline, is played more than [270
million times][chrome-dino-game] a month. This impressive number shows that
network reliability is a considerable opportunity for PWA use, especially in
markets with unreliable networks or expensive mobile data such as India, Brazil,
Mexico, or Indonesia.

When an app installed from an app store is launched, users expect
it to open regardless of whether they're connected to the internet.
Progressive Web Apps should be no different.

At a minimum, a simple offline page that tells the user the app isn't
available without a network connection should be served. Then, consider
taking the experience a step further by providing
[functionality that makes sense while offline][pwabook-ch6]. For example,
you could provide access to tickets or boarding passes, offline wish lists,
call center contact information, articles or recipes that the user has
recently viewed, etc.

<figure>
  {% Img src="image/admin/ubglZLCoddAfB5cl8JSz.jpg", alt="Be helpful, even when offline.", width="800", height="243" %}
  <figcaption>
    Be helpful, even when offline.
  </figcaption>
</figure>

Once you've implemented a [reliable user experience][pwabook-ch6], you may want
to measure it; how many users are going offline, in which geographies, and do
they stay on the website when the network is back? Analytics can tell you when
users go generally go [offline and online][pwabook-ch8]. This tells you how many
users continue browsing on your website after the network comes back.

<figure>
  {% Img src="image/admin/UfjYsWQWJjVIk2sp5bnE.jpg", alt="Trivago saw a 67% increase in users who came back online and continue browsing.", width="800", height="272" %}
  <figcaption>
    Trivago saw a 67% increase in users who came back online continue browsing.
  </figcaption>
</figure>

The [Trivago case study][twg-trivago] illustrates how this can impact your
business objectives. For users whose sessions were interrupted by an
offline period (around three percent of users), 67% of those who come
back online continued browsing the site.

### The business impact of an engaging website {: #impact-engaging }

Web push notifications allow users to opt-in to timely updates from sites they
need or care about and allow you to effectively re-engage them with customized,
relevant content.

Be careful, though. Asking users to sign up for web notifications when they
first arrive and without exposing the benefits can be perceived as spammy
and negatively affect your experience. Make sure to follow
[best practices][not-ux-bp] when prompting for notifications and inspire
acceptance through relevant usages like train delays, price tracking, out
of stock products, etc.

Technically, [push notifications][dgc-web-push] on the web run in the
background thanks to a service worker and are often sent by a system built for
managing campaigns (e.g. Firebase). This feature brings great business value
for mobile (Android) and desktop users. It increases repeated visits and
consequently sales and conversions.

To measure the effectiveness of your push campaigns, you need to measure
the whole funnel including:

* Number of users eligible for push notifications
* Number of users who click a custom notification UI prompt
* Number of users who grant push notification permission
* Number of users who receive push notifications
* Number of users who engage with notifications
* Conversion and engagement of users coming from a notification

<figure>
  {% Img src="image/admin/UpzfxBDi3e66cZ9gzkkS.jpg", alt="Measure the value of web push notifications.", width="800", height="255" %}
  <figcaption>
    Measure the value of web push notifications.
  </figcaption>
</figure>

There are a lot of great case studies on web push notifications, such as the one
with [Carrefour who multiplied their conversion rate by 4.5][carrefour-45x] by
re-engaging users with abandoned carts.

## The P in PWA: a progressive launch, feature by feature {: #feature-by-feature }

PWAs are modern websites that combine the massive reach of the web, combined
with all the user-friendly features that users expect in Android, iOS, or
desktop apps. They use a set of best practices and modern web APIs that can be
implemented independently depending on your business specificities and
priorities.

<figure>
  {% Img src="image/admin/7g1j2z7h5m9QSHQhHceM.jpg", alt="Progressively launch your PWA.", width="800", height="253" %}
  <figcaption>
    Progressively launch your PWA.
  </figcaption>
</figure>

To accelerate the modernization of your website and make it a real PWA,
we encourage you to be agile: launch feature by feature. First, research
with your users what features would bring them the most value, then deliver
them with your designers and developers, and finally do not forget to
measure precisely how much extra money your PWA generated.

[pwa-case-studies]: https://www.pwastats.com/
[precache-app]: /service-workers-cache-storage/
[wb-precache]: /precache-with-workbox/
[mdn-webpush]: https://developer.mozilla.org/docs/Web/API/Push_API/Best_Practices
[ten-things-we-know]: https://www.google.com/about/philosophy.html
[horror-movie]: https://blog.hubspot.com/marketing/mobile-website-load-faster
[deloitte-study]: https://www2.deloitte.com/ie/en/pages/consulting/articles/milliseconds-make-millions.html
[lighthouse]: https://developer.chrome.com/docs/lighthouse/overview/
[perf-budget-ci]: /incorporate-performance-budgets-into-your-build-tools/
[value-of-speed]: /value-of-speed/
[ebay-speed]: /shopping-for-speed-on-ebay/
[pwabook-ch4]: https://pwa-book.awwwards.com/chapter-4
[pwabook-ch6]: https://pwa-book.awwwards.com/chapter-6
[pwabook-ch8]: https://pwa-book.awwwards.com/chapter-8
[chrome-dino-game]: https://www.blog.google/products/chrome/chrome-dino/
[appinstalled-event]: /customize-install/#detect-install
[is-standalone]: /customize-install/#detect-launch-type
[twg-weekendest]: https://www.thinkwithgoogle.com/_qs/documents/8971/Weekendesk_PWA_-_EXTERNAL_CASE_STUDY.pdf
[twg-shopping]: https://www.thinkwithgoogle.com/data/smartphone-user-mobile-shopping-preferences/
[twg-uninstall]: https://www.thinkwithgoogle.com/data/why-users-uninstall-travel-apps/
[twg-purchase]: https://www.thinkwithgoogle.com/data/smartphone-mobile-app-and-site-purchase-data/
[twg-trivago]: https://www.thinkwithgoogle.com/intl/en-154/insights-inspiration/case-studies/trivago-embrace-progressive-web-apps-as-the-future-of-mobile/
[twg-notifications]: https://www.thinkwithgoogle.com/data/smartphone-user-notification-preferences/
[not-ux-bp]: /push-notifications-permissions-ux/
[dgc-web-push]: /notifications/
[carrefour-45x]: https://useinsider.com/case-studies/carrefour/
[cache-resources]: /service-workers-cache-storage/
[mdn-web-push]: https://developer.mozilla.org/docs/Web/API/Push_API/Best_Practices

# What does it take to be installable?

Progressive Web Apps (PWAs) are modern, high quality applications built
using web technology. PWAs offer similar capabilities to iOS/Android/desktop apps, they
are reliable even in unstable network conditions, and are installable making
it easier for users to find and use them.

Most users are familiar with installing applications, and the benefits of an
installed experience. Installed applications appear on operating system
launch surfaces, such as the Applications folder on Mac OS X, the Start menu
on Windows, and the home screen on Android and iOS. Installed applications
also show up in the activity switcher, device search engines such as
Spotlight and in content sharing sheets.

Most browsers indicate to the user that your Progressive Web App (PWA) is
installable when it meets certain criteria. Example indicators include an
Install button in the address bar, or an Install menu item in the overflow
menu.

<div class="switcher" >
  <figure id="browser-install-promo">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/O9KXz4aQXm3ZOzPo98uT.png", alt="Screenshot of omnibox with install indicator visible.", width="800", height="307" %}
    <figcaption>
      Browser provided install promotion (desktop)
    </figcaption>
  </figure>
  <figure>
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bolh05TCEeT7xni4eUTG.png", alt="Screenshot of browser provided install promotion.", width="800", height="307" %}
    <figcaption>
      Browser provided install promotion (mobile)
    </figcaption>
  </figure>
</div>

In addition, when the criteria is met, many browsers will fire a
`beforeinstallprompt` event, allowing you to provide a custom in-app UX that
that will trigger the install flow within your app.

## Install criteria {: #criteria }

In Chrome, your Progressive Web App must meet the following criteria before
it will fire the `beforeinstallprompt` event and show the in-browser install
promotion:

* The web app is not already installed
* Meets the user engagement heuristics:
  * The user needs to have clicked or tapped on the page at least once (at any time, even during a previous page load)
  * The user needs to have spent at least 30 seconds viewing the page (at any time)
* Be served over HTTPS
* Includes a [web app manifest][add-manifest] that includes:
  * `short_name` or `name`
  * `icons` - must include a 192px and a 512px icon
  * `start_url`
  * `display` - must be one of `fullscreen`, `standalone`, or `minimal-ui`
  * `prefer_related_applications` must not be present, or be `false`

Other browsers have similar criteria for installation, though there may be
minor differences. Check the respective sites for full details:

* [Edge](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps#requirements)
* [Firefox](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Installable_PWAs)
* [Opera](https://dev.opera.com/articles/installable-web-apps/)

{% Aside %}
On Android, if the web app manifest includes `related_applications` and
`"prefer_related_applications": true`, the user will be directed to the
Google Play store and
[prompted to install the specified Android app](https://developer.chrome.com/blog/app-install-banners-native/)
instead.
{% endAside %}

[add-manifest]: /add-manifest/
[offline-fallback]: /offline-fallback-page/
[new-offline-detection]: https://developer.chrome.com/blog/improved-pwa-offline-detection/

# How Chrome handles updates to the web app manifest

{% Aside %}
We are currently gathering data on browsers other than Chrome. If you would
like to help us gather this data or add content to this page, please leave a
comment in [issue #4038](https://github.com/GoogleChrome/web.dev/issues/4038).
{% endAside %}

When a PWA is installed, the browser uses information from the web app
manifest for the app name, the icons the app should use, and the URL that
should be opened when the app is launched. But what if you need to update
app shortcuts or try a new theme color? When and how are those changes
reflected in the browser?

{% Aside 'caution' %}
Do not change the name or location of your web app manifest file, doing so
may prevent the browser from updating your PWA.
{% endAside %}

In most cases, changes should be reflected within a day or two of the
PWA being launched, after the manifest has been updated.

## Updates on desktop Chrome {: #cr-desktop }

When the PWA is launched, or opened in a browser tab, Chrome determines the
last time the local manifest was checked for changes. If the manifest hasn't
been checked since the browser last started, or it hasn't been checked in the
last 24 hours, Chrome will make a network request for the manifest, then
compare it against the local copy.

If select properties in the manifest have changed (see list below), Chrome
queues the new manifest, and after all windows have been closed, installs it.
Once installed, all fields from the new manifest (except `name`, `short_name`,
and `icons`) are updated.

### Which properties will trigger an update? {: #cr-desktop-trigger }

* `display` (see below)
* `scope`
* `shortcuts`
* `start_url`
* `theme_color`
* `file_handlers`

{% Aside 'caution' %}
Changes to `name`, `short_name` and `icons` are **not**
currently supported on desktop Chrome, though work is underway to support them.
{% endAside %}

{% Aside 'caution' %}
Changes to the `start_url` require the manifest `id` to be set.
[More info](https://developer.chrome.com/blog/pwa-manifest-id/)
{% endAside %}

<!-- CrBug for name/shortname https://crbug.com/1088338 -->
<!-- CrBug for start_url https://crbug.com/1095947 -->

### What happens when the `display` field is updated?

If you update your app's display mode from `browser` to `standalone` your
existing users will not have their apps open in a window after updating. There
are two display settings for a web app, the one from the manifest (that you
control) and a window/browser tab setting controlled by the user. The user
preference is always respected.

### Testing manifest updates {: #cr-desktop-test }

The `chrome://web-app-internals` page (available in Chrome 85 or later),
includes detailed information about all of the PWAs installed on the device,
and can help you understand when the manifest was last updated, how often
it's updated, and more.

To manually force Chrome to check for an updated manifest, you can either launch
Chrome with the [command line flag][flag] `--disable-manifest-update-throttle`
or restart Chrome (use `about://restart`), this resets the timer so that Chrome
will check for an updated manifest when the PWA is next launched. Then launch
the PWA. After closing the PWA, it should be updated with the new manifest
properties.

### References {: #cr-desktop-ref }

* [Updatable Web Manifest Fields][updatable-manifest-doc]

## Updates on Chrome for Android {: #cr-android }

When the PWA is launched, Chrome determines the last time the local manifest
was checked for changes. If the manifest hasn't been checked in the last 24
hours, Chrome will schedule a network request for the manifest, then compare
it against the local copy.

If select properties in the manifest have changed (see list below), Chrome
queues the new manifest, and after all windows of the PWA have been closed,
the device is plugged in, and connected to WiFi, Chrome requests an updated
WebAPK from the server. Once updated, all fields from the new manifest are
used.

### Which properties will trigger an update? {: #cr-android-trigger }

* `background_color`
* `display`
* `orientation`
* `scope`
* `shortcuts`
* `start_url`
* `theme_color`
* `web_share_target`

If Chrome is unable to get an updated manifest from the server, it may
increase the time between checks to 30 days.

{% Aside 'caution' %}
Changes to `name`, `short_name` and `icons` are **not** currently supported
on Android Chrome, though work is underway to support them.
{% endAside %}

### Testing manifest updates {: #cr-android-test }

The `about://webapks` page includes detailed information about all of the
PWAs installed on the device, and can tell you when the manifest was last
updated, how often it's updated, and more.

To manually schedule an update to the manifest, overriding the timer and
local manifest do the following:

1. Plug in the device and ensure it's connected to WiFi.
2. Use the Android task manager to shut down the PWA, then use the App panel
   in Android settings to force stop the PWA.
3. In Chrome, open `about://webapks` and click the "Update" button for the
   PWA. "Update Status" should change to "Pending".
4. Launch the PWA, and verify it's loaded properly.
5. Use the Android task manager to shut down the PWA, then use the App panel
   in Android settings to force stop the PWA.

The PWA usually updates within a few minutes, once the update has completed,
"Update Status" should change to "Successful"

### References {: #cr-android-ref }

* [`UpdateReason` enum][update-enum] for Chrome on Android

[flag]: https://www.chromium.org/developers/how-tos/run-chromium-with-flags
[updatable-manifest-doc]: https://docs.google.com/document/d/1twU_yAoTDp4seZMmqrDzJFQtrM7Z60jXHkXjMIO2VpM/preview
[update-enum]: https://cs.chromium.org/chromium/src/chrome/browser/android/webapk/webapk.proto?l=35

# Create an offline fallback page

What do the Google Assistant app, the Slack app, the Zoom app, and almost
any other platform-specific app on your phone or computer have in common? Right, they always at least give you *something*.
Even when you do not have a network connection, you can still open the Assistant app, or enter
Slack, or launch Zoom. You might not get anything particularly meaningful or even
be unable to achieve what you wanted to achieve, but at least you get _something_ and the app is in
control.

<figure role="group" aria-labelledby="fig-apps-wrapper">

  <figure role="group" aria-labelledby="fig-assistant" style="display: inline-block">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg", alt="Google Assistant mobile app while offline.", width="621", height="1344" %}
    <figcaption id="fig-assistant">
      Google Assistant.
    </figcaption>
  </figure>

  <figure role="group" aria-labelledby="fig-slack" style="display: inline-block">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg", alt="Slack mobile app while offline.", width="621", height="1344" %}
    <figcaption id="fig-slack">
      Slack.
    </figcaption>
  </figure>

  <figure role="group" aria-labelledby="fig-zoom" style="display: inline-block">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg", alt="Zoom mobile app while offline.", width="621", height="1344" %}
    <figcaption id="fig-zoom">
      Zoom.
    </figcaption>
  </figure>

  <figcaption id="fig-apps-wrapper">
    With platform-specific apps, even when you do not have a network connection, you never get nothing.
  </figcaption>

</figure>

In contrast, on the Web, traditionally you get nothing when you are offline. Chrome gives you
the [offline dino game](https://www.blog.google/products/chrome/chrome-dino/), but that is it.

<figure role="group" aria-labelledby="fig-offline-wrapper">

  <figure role="group" aria-labelledby="fig-chrome-ios" style="display: inline-block">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png", alt="Google Chrome mobile app showing the offline dino game.", width="800", height="1731" %}
    <figcaption id="fig-chrome-ios">
      Google Chrome for iOS.
    </figcaption>
  </figure>

  <figure role="group" aria-labelledby="fig-chrome" style="display: inline-block">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png", alt="Google Chrome desktop app showing the offline dino game.", width="800", height="607" %}
    <figcaption id="fig-chrome">
      Google Chrome for macOS.
    </figcaption>
  </figure>

  <figcaption id="fig-offline-wrapper">
    On the Web, when you do not have a network connection, by default you get nothing.
  </figcaption>

</figure>

## An offline fallback page with a custom service worker

It does not have to be this way, though. Thanks to
[service workers and the Cache Storage API](/service-workers-cache-storage/), you can provide a
customized offline experience for your users. This can be a simple branded page with the information
that the user is currently offline, but it can just as well be a more creative solution, like, for
example, the famous [trivago offline maze game](https://www.trivago.com/offline) with a manual
**Reconnect** button and an automatic reconnection attempt countdown.

<figure>
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png", alt="The trivago offline page with the trivago offline maze.", width="800", height="616" %}
    <figcaption>
      The trivago offline maze.
    </figcaption>
  </figure>

### Registering the service worker

The way to make this happen is through a service worker. You can register a service worker
from your main page as in the code sample below. Usually you do this once
your app has loaded.

```js
window.addEventListener("load", () => {
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("service-worker.js");
  }
});
```

### The service worker code

The contents of the actual service worker file may seem a little involved at first sight, but the
comments in the sample below should clear things up. The core idea is to pre-cache a file named
`offline.html` that only gets served on _failing_ navigation requests, and to let the browser handle
all other cases:

```js
/*
Copyright 2015, 2019, 2020, 2021 Google LLC. All Rights Reserved.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
// This variable is intentionally declared and unused.
// Add a comment for your linter if you want:
// eslint-disable-next-line no-unused-vars
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
// Customize this with a different URL if needed.
const OFFLINE_URL = "offline.html";

self.addEventListener("install", (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      // Setting {cache: 'reload'} in the new request ensures that the
      // response isn't fulfilled from the HTTP cache; i.e., it will be
      // from the network.
      await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
    })()
  );
  // Force the waiting service worker to become the active service worker.
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      // Enable navigation preload if it's supported.
      // See https://developers.google.com/web/updates/2017/02/navigation-preload
      if ("navigationPreload" in self.registration) {
        await self.registration.navigationPreload.enable();
      }
    })()
  );

  // Tell the active service worker to take control of the page immediately.
  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === "navigate") {
    event.respondWith(
      (async () => {
        try {
          // First, try to use the navigation preload response if it's
          // supported.
          const preloadResponse = await event.preloadResponse;
          if (preloadResponse) {
            return preloadResponse;
          }

          // Always try the network first.
          const networkResponse = await fetch(event.request);
          return networkResponse;
        } catch (error) {
          // catch is only triggered if an exception is thrown, which is
          // likely due to a network error.
          // If fetch() returns a valid HTTP response with a response code in
          // the 4xx or 5xx range, the catch() will NOT be called.
          console.log("Fetch failed; returning offline page instead.", error);

          const cache = await caches.open(CACHE_NAME);
          const cachedResponse = await cache.match(OFFLINE_URL);
          return cachedResponse;
        }
      })()
    );
  }

  // If our if() condition is false, then this fetch handler won't
  // intercept the request. If there are any other fetch handlers
  // registered, they will get a chance to call event.respondWith().
  // If no fetch handlers call event.respondWith(), the request
  // will be handled by the browser as if there were no service
  // worker involvement.
});
```

### The offline fallback page

The `offline.html` file is where you can get creative and adapt it to your needs and add your
branding. The example below shows the bare minimum of what is possible.
It demonstrates both manual reload based on a button press as well as automatic reload
based on the [`online` event](https://developer.mozilla.org/docs/Web/API/Window/online_event)
and regular server polling.

{% Aside 'gotchas' %} You need to cache all resources required by your offline page. One
way to deal with this is to inline everything, so the offline page is self-contained. This is what I
do in the example below. {% endAside %}

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>You are offline</title>

    <!-- Inline the page's stylesheet. -->
    <style>
      body {
        font-family: helvetica, arial, sans-serif;
        margin: 2em;
      }

      h1 {
        font-style: italic;
        color: #373fff;
      }

      p {
        margin-block: 1rem;
      }

      button {
        display: block;
      }
    </style>
  </head>
  <body>
    <h1>You are offline</h1>

    <p>Click the button below to try reloading.</p>
    <button type="button">⤾ Reload</button>

    <!-- Inline the page's JavaScript file. -->
    <script>
      // Manual reload feature.
      document.querySelector("button").addEventListener("click", () => {
        window.location.reload();
      });

      // Listen to changes in the network state, reload when online.
      // This handles the case when the device is completely offline.
      window.addEventListener('online', () => {
        window.location.reload();
      });

      // Check if the server is responding and reload the page if it is.
      // This handles the case when the device is online, but the server
      // is offline or misbehaving.
      async function checkNetworkAndReload() {
        try {
          const response = await fetch('.');
          // Verify we get a valid response from the server
          if (response.status >= 200 && response.status < 500) {
            window.location.reload();
            return;
          }
        } catch {
          // Unable to connect to the server, ignore.
        }
        window.setTimeout(checkNetworkAndReload, 2500);
      }

      checkNetworkAndReload();
    </script>
  </body>
</html>
```

## Demo

You can see the offline fallback page in action in the
[demo](https://offline-fallback-demo.glitch.me/index.html) embedded below. If you are
interested, you can explore the [source code](https://glitch.com/edit/#!/offline-fallback-demo) on
Glitch.

<div class="glitch-embed-wrap" style="height: 420px; width: 100%;">
  <iframe
    src="https://glitch.com/embed/#!/embed/offline-fallback-demo?path=offline.html&previewSize=100"
    title="offline-fallback-demo on Glitch"
    allow="geolocation; microphone; camera; midi; vr; encrypted-media"
    style="height: 100%; width: 100%; border: 0;">
  </iframe>
</div>

### Side note on making your app installable

Now that your site has an offline fallback page, you might wonder about next steps. To make
your app installable, you need to add a [web app manifest](/add-manifest/) and optionally come up
with an [install strategy](/define-install-strategy/).

### Side note on serving an offline fallback page with Workbox.js

You may have heard of [Workbox](https://developer.chrome.com/docs/workbox/).
Workbox is a set of JavaScript libraries for adding offline support to web apps. If you prefer to
write less service worker code yourself, you can use the Workbox recipe for an
[offline page only](https://developer.chrome.com/docs/workbox/managing-fallback-responses/#offline-page-only).

Up next, learn [how to define an install strategy](/define-install-strategy/) for your app.

# Patterns for promoting PWA installation

Installing your Progressive Web App (PWA) can make it easier for users to
find and use. Even with browser promotion, some users don't realize that
they can install a PWA, so it can be helpful to provide an in-app experience
that you can use to promote and enable installation of your PWA.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PtJp54jasjOYyh9Soqzu.png", alt="Screenshot of simple install button in PWA.", width="800", height="368" %}
  <figcaption>
    A simple install button provided within your PWA.
  </figcaption>
</figure>

This article is by no means exhaustive, but offers a starting point for different
ways to promote the installation of your PWA. Regardless of which pattern, *or
patterns* you use,  they all lead to the same code that triggers the install
flow, documented in
[How to provide your own in-app install experience](/customize-install/).

## Best practices {: #best-practices }

There are some best practices that apply no matter which promotional patterns
you're using on your site.

* Keep promotions outside of the flow of your user journeys. For example,
  in a PWA login page, put the call to action below the login form and submit
  button. Disruptive use of promotional patterns reduces the usability of your
  PWA and negatively impacts your engagement metrics.
* Include the ability to dismiss or decline the promotion. Remember the
  user's preference if they do this and only re-prompt if there's a change
  in the user's relationship with your content such as if they signed in or
  completed a purchase.
* Combine techniques in different parts of your PWA, but be careful not to
  overwhelm or annoy your user with install promotion.
* Only show the promotion **after** the
  [`beforeinstallprompt` event](/customize-install/#beforeinstallprompt) has
  fired.

## Automatic browser promotion {: #browser-promotion }

When [certain criteria](/install-criteria/) are met, most browsers automatically
indicate to the user that your Progressive Web App is installable. For example,
desktop Chrome shows an install button in the omnibox.

<div class="switcher">
  <figure id="browser-install-promo">
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zIfRss5zOrZ49c4VdJ52.png", alt="Screenshot of omnibox with a visible install indicator.", width="800", height="307" %}
    <figcaption>
      Browser-provided install promotion (desktop).
    </figcaption>
  </figure>
  <figure>
    {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kRjcsxlHDZa9Nqg2Fpei.png", alt="Screenshot of browser-provided install promotion.", width="800", height="307" %}
    <figcaption>
      Browser-provided install promotion (mobile).
    </figcaption>
  </figure>
</div>

Chrome for Android shows a mini infobar to the user, though this can be
prevented by calling `preventDefault()` on the `beforeinstallprompt` event.
If you do not call `preventDefault()`, the banner will be shown the first time
a user visits your site and meets the installability criteria on Android,
and then again after approximately 90 days.

## User interface promotional patterns {: #app-ui-patterns }

User interface promotional patterns can be used for almost any kind of PWA and
appear in places such as site navigation and banners. As with
any other type of promotional pattern, it's important to be aware of the
user's context to minimize disruption of the user's journey.

Sites which are thoughtful about when they trigger promotion UI achieve a
larger number of installs and avoid interfering with the journeys of users
who aren't interested in installation.

### Simple install button {: #simple-button }

The simplest possible UX is to include an 'Install' or 'Get app' button at an
appropriate location in your web content. Ensure the button doesn't block
other important functionality and is out of the way of the user's journey
through your application.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kv0x9hxZ0TLVaIiP4Bqx.png", alt="Custom install button..", width="800", height="448" %}
  <figcaption>
    Simple install button.
  </figcaption>
</figure>

### Fixed header {: #header }

This is an install button that is part of the header of your site. Other
header content often includes site branding such as a logo and the hamburger
menu. Headers may be `position:fixed` or not depending on your site's
functionality and user needs.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GPJdkXcpNLR30r2zo7RR.png", alt="Custom install button in header.", width="800", height="448" %}
  <figcaption>
    Custom install button in header.
  </figcaption>
</figure>

When used appropriately, promoting PWA installation from the header of your
site is a great way to make it easier for your most loyal customers to
return to your experience. Pixels in your PWA header are precious,
so make sure your installation call to action is appropriately sized, of greater
importance than other possible header content, and unintrusive.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L01AoSoy7LNk1ttMMax0.png", alt="Custom install button in header", width="800", height="430" %}
  <figcaption>
    Custom install button in header
  </figcaption>
</figure>

Make sure you:

* Do not show the install button unless the `beforeinstallprompt` has been
  fired.
* Evaluate the value of your installed use case for your users. Consider
  selective targeting to only present your promotion for users that are
  likely to benefit from it.
* Use precious header space efficiently. Consider what else would be helpful
  to offer your user in the header, and weigh the priority of the install
  promotion relative to other options.

### Navigation menu {: #nav }

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aT7NHi8lbsZW8TOm3Gaw.png", alt="Custom install button in nav menu", width="800", height="1117" %}
  <figcaption>
    Add an install button/promotion in a slide out navigation menu.
  </figcaption>
</figure>

The navigation menu is a great place for promoting the installation of your
app since users who open the menu are signaling engagement with your experience.

Make sure you:

* Avoid disrupting important navigational content. Put the PWA install
  promotion below other menu items.
* Offer a short, relevant pitch for why the user would benefit from
  installing your PWA.

### Landing page {: #landing }

The purpose of a landing page is to promote your products and services,
so this is one place where it is appropriate to go large when promoting
the benefits of installing your PWA.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7q09M12HFxgIiWhKPGma.png", alt="Custom install prompt on a landing page.", width="800", height="1117" %}
  <figcaption>
    Custom install prompt on a landing page.
  </figcaption>
</figure>

First, explain your site's value proposition, then let visitors know what
they'll get from installation.

Make sure you:

* Appeal to features that matter most to your visitors and emphasize
  keywords that might have brought them to your landing page.
* Make your install promotion and call to action eye catching, but only after
  you've made your value proposition clear. This is your landing page, after
  all.
* Consider adding an install promotion in the part of your app where users spend
  most of their time.

### Install banner {: #banner }

Most users have encountered installation banners in mobile experiences and are
familiar with the interactions offered by a banner. Banners should be used
carefully because they can disrupt the user.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7fLCQQhdk2OzrQD3Xh4E.png", alt="Custom install banner at top of page.", width="800", height="1000" %}
  <figcaption>
    A dismissible banner at the top of the page.
  </figcaption>
</figure>

Make sure you:

* Wait until the user has demonstrated interest in your site before showing
  a banner. If the user dismisses your banner, don't show it again unless
  the user triggers a conversion event that indicates a higher level of
  engagement with your content such as a purchase on an e&#8209;commerce site or
  signing up for an account.
* Provide a brief explanation of the value of installing your PWA in the
  banner. For example, you can differentiate the install of a PWA from an
  iOS/Android app by mentioning that it uses almost no storage on the user's
  device or that it will install instantly without a store redirect.

### Temporary UI {: #temporary-ui }

Temporary UI, such as the [Snackbar](https://material.io/components/snackbars/)
design pattern, notifies the user, and allows them to easily complete an
action, in this case, installing the app. When used properly, these kinds of UI
patterns don't interrupt the user flow, and are typically automatically
dismissed if ignored by the user.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/6DySYRtyegazEfMcWXQL.png", alt="Custom install banner as snackbar.", width="800", height="448" %}
  <figcaption>
    A dismissible snackbar indicating the PWA is installable.
  </figcaption>
</figure>

Show the snackbar after a few interactions with your app. If it
appears on  page load, or out of context, it can be easily missed, or lead
to cognitive overload. When this happens, users will simply dismiss everything
they see. Remember, new users to your site may not be ready to install your
PWA. Therefore, it's best to wait until you have strong interest signals from
the user before using this pattern, for example, repeat visits, a user sign
in, or similar conversion event.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/d8dwdIe1rYSgd0JdCGtt.png", alt="Custom install banner as snackbar.", width="800", height="424" %}
  <figcaption>
    A dismissible snackbar indicating the PWA is installable.
  </figcaption>
</figure>

Make sure you:

* Show the snackbar for between 4 and 7 seconds to give users enough
  time to see and react to it, and without getting in the way.
* Avoid showing it over other temporary UI such as banners, etc.
* Wait until you have strong interest signals from the user before
  using this pattern, for example, repeat visits, a user sign in,
  or similar conversion event.

## After conversion

Immediately after a user conversion event, for example after a purchase on
an e&#8209;commerce site, is an excellent opportunity to promote the installation of
your PWA. The user is clearly engaged with your content, and a conversion
often signals that the user will engage with your services again.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DrepSPFAm64d5cvTFoXe.png", alt="An install promotion after conversion.", width="800", height="448" %}
  <figcaption>
    An install promotion after a user has completed a purchase.
  </figcaption>
</figure>

### Booking or checkout journey {: #journey }

Show an install promotion during or after a sequential journey such as after
booking or checkout flows. If you're displaying the promotion after the user
has completed the journey, you can often make it more prominent since the
journey is completed.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bOYZM2UiWK5itVSpjKWO.png", alt="An install promotion after a user journey.", width="800", height="1419" %}
  <figcaption>
    An install promotion after a user journey.
  </figcaption>
</figure>

Make sure you:

* Include a relevant call to action. Which users will benefit from installing
  your app and why? How is it relevant to the journey they are currently on?
* If your brand has unique offers for installed app users, mention them.
* Keep the promotion out of the way of next steps in your journey or you can
  negatively affect journey completion rates. In the e&#8209;commerce example
  above, notice how the key call to action to checkout is above the
  app install promotion.

### Sign up, sign in, or sign out flow {: #sign-up}

This promotion is a special case of the [journey](#journey) promotional
pattern where the promotion card can be more prominent.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQXqSqtwRSwyELdJMjtd.png", alt="A custom install button on the sign up page.", width="800", height="1117" %}
  <figcaption>
    A custom install button on the sign up page.
  </figcaption>
</figure>

These pages are usually only viewed by engaged users, where the value
proposition of your PWA has already been established. There's also often not
a lot of other useful content to place on these pages. As a result, it's less
disruptive to make a larger call-to-action as long as it's not in the way.

Make sure you:

* Avoid disrupting the user's journey inside the sign up form. If it's a
  multi-step process, you might want to wait until the user has completed the
  journey.
* Promote features most relevant to a signed-up user.
* Consider adding an additional install promotion within the signed-in areas
  of your app.

## Inline promotional patterns

Inline promotional techniques interweave promotions with site content. This
is often more subtle than promotion in the user interface, which has tradeoffs.
You want your promotion to stand out enough that interested users will notice
it, but not so much that it detracts from the quality of your user experience.

### In-feed {: #in-feed }

An in-feed install promotion appears between news articles or other lists of
information cards in your PWA.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LS5qSE2vicfjRBBkA47a.png", alt="An install promotion within a content feed.", width="800", height="1000" %}
  <figcaption>
    An install promotion within a content feed.
  </figcaption>
</figure>

Your goal is to show users how to access the content they're enjoying more
conveniently. Focus on promoting features and functionality that will be
helpful to your users.

Make sure you:

* Limit the frequency of the promotions to avoid annoying users.
* Give your users the ability to dismiss the promotions.
* Remember your user's choice to dismiss.

# What makes a good Progressive Web App?

<!-- Disable heading-increment because it mucks with the Details widget -->
<!--lint disable heading-increment-->

Progressive Web Apps (PWA) are built and enhanced with modern APIs to deliver
enhanced capabilities, reliability, and installability while reaching
_anyone, anywhere, on any device_ with a single codebase. To help you create
the best possible experience, use the [core](#core) and [optimal](#optimal)
checklists and recommendations to guide you.

## Core Progressive Web App checklist {: #core }

The Progressive Web App Checklist describes what makes an app installable and
usable by all users, regardless of size or input type.

{% Details %}
{% DetailsSummary 'h3' %}

Starts fast, stays fast

Performance plays a significant role in the success of any online experience,
because high performing sites engage and retain users better than poorly
performing ones. Sites should focus on optimizing for user-centric performance metrics.

{% endDetailsSummary %}

Performance plays a significant role in the success of any online experience,
because high performing sites engage and retain users better than poorly
performing ones. Sites should focus on optimizing for user-centric performance metrics.

#### Why

Speed is critical for getting users to _use_ your app.
In fact, as page load times go from one second to ten seconds, the probability of
a user bouncing increases by 123%. Performance doesn't stop with the `load` event.
Users should never wonder whether their interaction—for example, clicking a
button—was registered or not. Scrolling and animation should feel smooth.
Performance affects your entire experience, from how users perceive your
application to how it actually performs.

While all applications have different needs, the performance audits in
Lighthouse are based on the
[Core Web Vitals](/vitals/),
and scoring high on those audits will make it more likely that your users have
an enjoyable experience. You can also use
[PageSpeed Insights](https://pagespeed.web.dev/)
or the [Chrome User Experience Report](https://developer.chrome.com/docs/crux/)
to get real-world performance data for your web app.

#### How

Follow our [guide on fast load times](/fast/) to learn how to make your PWA
start fast and stay fast.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Works in any  browser

Users can use any browser they choose to access your web app before it's
installed.

{% endDetailsSummary%}

Users can use any browser they choose to access your web app before it's
installed.

#### Why

Progressive Web Apps are web apps first, and that means they need to work
across browsers, not just in one of them.

An effective way of doing this is by, in the words of Jeremy Keith in
[Resilient Web Design](https://resilientwebdesign.com/), identifying the core
functionality, making that functionality available using the simplest possible
technology, and then enhancing the experience where possible. In many cases,
this means starting with just HTML to create the core functionality, and
enhancing the user experience with CSS and JavaScript to create a more
engaging experience.

Take form submission for example. The simplest way to implement that is an
HTML form that submits a `POST` request. After building that, you can enhance
the experience with JavaScript to do form validation and submit the form via
AJAX, improving the experience for users who can support it.

Consider that your users will experience your site across a spectrum of
devices and browsers. You cannot simply target the top end of the spectrum.
By using feature detection, you'll be able to deliver a usable experience
for the widest range of potential users, including those using browsers
and devices that may not exist today.

#### How

Jeremy Keith's [Resilient Web Design](https://resilientwebdesign.com/) is an
excellent resource describing how to think about web design in this
cross-browser, progressive methodology.

#### Additional reading

* A List Apart's [Understanding Progressive Enhancement](https://alistapart.com/article/understandingprogressiveenhancement/)
  is a good introduction to the topic.
* Smashing Magazine's
  [Progressive Enhancement: What It Is, And How To Use It?](https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/)
  gives a practical introduction and links to more advanced topics.
* MDN has an article titled
  [Implementing feature detection](https://developer.mozilla.org/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection)
  that talks about how to detect a feature  by directly querying it.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Responsive to any screen size

Users can use your PWA on any screen size and all of the content is available
at any viewport size.

{% endDetailsSummary %}

Users can use your PWA on any screen size and all of the content is available
at any viewport size.

#### Why

Devices come in a range of sizes, and users may use your application at a
range of sizes, even on the same device. Therefore, it's critical to ensure
your content not only fits within the viewport, but that all features and
content for your site are usable at all viewport sizes.

The tasks users want to complete and the content they want to access do
not change with viewport size. The content can be rearranged at different
viewport sizes, and it should all be there, one way or another. In fact, as
Luke Wroblewski states in his book Mobile First, starting small and going
large instead of the other way around can actually improve a site's design:

> Mobile devices require software development teams to focus on only the
> most important data and actions in an application. There simply isn't room
> in a 320 by 480 pixel screen for extraneous, unnecessary elements.
> You have to prioritize.

#### How

There are many  resources on responsive design, including the
[original article by Ethan Marcotte](https://alistapart.com/article/responsive-web-design/),
a [collection of important concepts](https://snugug.com/musings/principles-responsive-web-design/)
related to it, as well as books and talks galore. To narrow this discussion
down to the content aspects of responsive design, you can dig into
[content-first design](https://uxdesign.cc/why-you-should-design-the-content-first-for-better-experiences-374f4ba1fe3c)
and [content-out responsive layouts](https://alistapart.com/article/content-out-layout/).
Finally, while it's focused on mobile, the lessons in
[Seven Deadly Mobile Myths](https://www.forbes.com/sites/anthonykosner/2012/05/03/seven-deadly-mobile-myths-josh-clark-debunks-the-desktop-paradigm-and-more/#21ecac977bca)
by Josh Clark are just as relevant to small-sized views of responsive sites
as they are to mobile.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Provides a custom offline page

When users are offline, keeping them in your PWA provides a more seamless
experience than dropping back to the default browser offline
page.

{% endDetailsSummary %}

When users are offline, keeping them in your PWA provides a more seamless
experience than dropping back to the default browser offline
page.

#### Why

Users expect installed apps to work regardless of their connection status.
A platform-specific app never shows a blank page when it is offline, and a Progressive
Web App should never show the browser default offline page. Providing a
custom offline experience, both when a user navigates to a URL that hasn't
been cached and when a user tries to use a feature that requires a connection,
helps your web experience feel like it's part of the device it's running on.

#### How

During a service worker's `install` event, you can precache a custom offline
page for later use. If a user goes offline, you can respond with the precached
custom offline page. You can follow our custom
[offline page sample](https://googlechrome.github.io/samples/service-worker/custom-offline-page/)
to see an example of this in action and learn how to implement it yourself.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Is installable

Users who install or add apps to their device tend to engage with those
apps more.

{% endDetailsSummary %}

Users who install or add apps to their device tend to engage with those
apps more.

#### Why

Installing a Progressive Web App allows it to look, feel, and behave like all
other installed apps. It's launched from the same place users launch their
other apps. It runs in its own app window, separate from the browser, and it
appears in the task list, just like other apps.

Why would you want a user to install your PWA? The same reason you'd want a
user to install your app from an app store. Users who install your apps
are your most engaged audience, and have better engagement metrics than
typical visitors, often at parity with app users on mobile devices.
These metrics include more repeat visits, longer times on your site and
higher conversion rates.

#### How

You can follow our [installable guide](/customize-install/) to learn how to make
your PWA installable, how to test to see that it's installable, and try
doing it yourself.

{% endDetails %}

## Optimal Progressive Web App checklist {: #optimal }

To create a truly great Progressive Web App, one that feels like a
best-in-class app, you need more than just the core checklist. The optimal
Progressive Web App checklist is about making your PWA feel like it's part
of the device it's running on while taking advantage of what makes the web powerful.


{% Details %}
{% DetailsSummary 'h3' %}

Provides an offline experience

Where connectivity isn't strictly required, your app works the same offline
as it does online.

{% endDetailsSummary %}

Where connectivity isn't strictly required, your app works the same offline
as it does online.

#### Why

In addition to providing a custom offline page, users expect Progressive Web
Apps to be usable offline. For example, travel and airline apps should have
trip details and boarding passes easily available when offline. Music,
video, and podcasting apps should allow for offline playback. Social and
news apps should cache recent content so users can read it when offline.
Users also expect to stay authenticated when offline, so design for
offline authentication. An offline PWA provides a true app-like experience
for users.

#### How

After determining which features your users expect to work offline, you'll
need to make your content available and adaptable to offline contexts. In
addition, you can use
[IndexedDB](/indexeddb/),
an in-browser NoSQL storage system, to store and retrieve data, and
[background sync](https://developer.chrome.com/blog/background-sync/)
to allow users to take actions while offline and defer server communications
until the user has a stable connection again. You can also use service
workers to store other kinds of content, such as images, video files, and
audio files for offline use, as well as use them to implement
[safe, long-lived sessions](https://developer.chrome.com/blog/2-cookie-handoff/)
to keep users authenticated. From a user experience perspective, you can use
[skeleton screens](https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a)
that give users a perception of speed and content while loading that can
then fall back to cached content or an offline indicator as needed.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Is fully accessible

All user interactions pass [WCAG 2.0](https://www.w3.org/TR/WCAG20/)
accessibility requirements.

{% endDetailsSummary %}

All user interactions pass [WCAG 2.0](https://www.w3.org/TR/WCAG20/)
accessibility requirements.

#### Why

Most people at some point in their life will want to take advantage of your
PWA in a way that is covered under the [WCAG 2.0](https://www.w3.org/TR/WCAG20/)
accessibility requirements. Humans' ability to interact with and understand
your PWA exist on a spectrum and needs can be temporary or permanent. By
making your PWA accessible, you ensure it's usable for everyone.

#### How

W3C's [Introduction to Web Accessibility](https://www.w3.org/WAI/fundamentals/accessibility-intro/)
is a good place to start. A majority of accessibility testing
must be done manually. Tools like the [Accessibility](https://developer.chrome.com/docs/lighthouse/accessibility/)
audits in Lighthouse, [axe](https://github.com/dequelabs/axe-core), and
[Accessibility Insights](https://accessibilityinsights.io/) can help you
automate some accessibility testing. It's also important to use semantically
correct elements instead of recreating those elements on your own,
for instance, `a` and `button` elements. This ensures that when you do need
to build more advanced functionality, accessible expectations are met (such as
when to use arrows vs. tabs).
[A11Y Nutrition Cards](https://accessibilityinsights.io/) has excellent
advice on this for some common components.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Can be discovered through search

Your PWA can be easily [discovered through search](/discoverable/).

{% endDetailsSummary %}

Your PWA can be easily [discovered through search](/discoverable/).

#### Why

One of the biggest advantages of the web is the ability to discover sites and
apps through search. In fact,
[more than half](https://www.brightedge.com/resources/research-reports/channel_share)
of all website traffic comes from organic search. Making sure that canonical
URLs exist for content and that search engines can index your site is critical
for users to be able to find your PWA. This is especially true when adopting
client-side rendering.

#### How

Start by ensuring that each URL has a unique, descriptive title and meta
description. Then you can use the
[Google Search Console](https://search.google.com/search-console/about)
and the [Search Engine Optimization audits](https://developer.chrome.com/docs/lighthouse/seo/) in Lighthouse to
help you debug and fix discoverability issues with your PWA. You can also use
[Bing](https://www.bing.com/toolbox/webmaster)'s or
[Yandex](https://webmaster.yandex.com/welcome/)'s webmaster tools, and
consider including [structured data](https://goo.gle/search-gallery) via
schemas from [Schema.org](https://schema.org/) in your PWA.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Works with any input type

Your PWA is equally usable with a mouse, a keyboard, a stylus, or touch.

{% endDetailsSummary %}

Your PWA is equally usable with a mouse, a keyboard, a stylus, or touch.

#### Why

Devices offer a variety of input methods, and users should be able to
seamlessly switch between them while using your application. Just as important,
input methods should not depend on screen size, meaning that large viewports
need to support touch and small viewports need to support keyboards and mice.
To the best of your ability, ensure that your application and all of its
features supports usage of any input method your user may choose to use.
Where appropriate, you should also enhance experiences to allow for
input-specific controls as well (such as pull-to-refresh).

#### How

The [Pointer Events API](https://developer.chrome.com/blog/pointer-events/)
provides a unified interface for working with various input options, and is
especially good for adding stylus support. For supporting both touch and
keyboard, ensure that you're using the correct semantic elements (anchors,
buttons, form controls, etc.) and not rebuilding these with non-semantic
HTML (which is good for accessibility). When including interactions that
activate on hover, ensure they can activate on click or tap, too.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Provides context for permission requests

When asking permission to use powerful APIs, provide context and ask only
when the API is needed.

{% endDetailsSummary %}

When asking permission to use powerful APIs, provide context and ask only
when the API is needed.

#### Why

APIs that trigger a permission prompt, like notifications, geolocation, and
credentials, are intentionally designed to be disruptive to a user because they
tend to be related to powerful functionality that requires opt-in. Triggering
these prompts without additional context, like on page load, makes users
less likely to accept those permissions and more likely to distrust them
in the future. Instead, only trigger those prompts after providing an
in-context rationale to the user for why you need that permission.

#### How

The [Permission UX](/push-notifications-permissions-ux/)
article and UX Planet's
[The Right Ways to Ask Users for Permissions](https://uxplanet.org/mobile-ux-design-the-right-ways-to-ask-users-for-permissions-6cdd9ab25c27)
are good resources to understand how to design permission prompts that, while
focused on mobile, apply to all PWAs.

{% endDetails %}


{% Details %}
{% DetailsSummary 'h3' %}

Follows best practices for healthy code

Keeping your codebase healthy makes it easier to meet your goals and deliver
new features.

{% endDetailsSummary %}

Keeping your codebase healthy makes it easier to meet your goals and deliver
new features.

#### Why

There's a lot that goes into building a modern web application. Keeping your
application up-to-date and your codebase healthy makes it easier for you to
deliver new features that meet the other goals laid out in this checklist.

#### How

There are a number of high-priority checks to ensure a healthy
codebase: avoiding using libraries with known vulnerabilities, ensuring
you're not using deprecated APIs, removing web anti-patterns from your
codebase (like using `document.write()` or having non-passive scroll event
listeners), and even coding defensively to ensure your PWA doesn't break
if analytics or other third party libraries fail to load. Consider requiring
static code analysis, like linting, as well as automated testing, in multiple
browsers and release channels. These techniques can help catch errors
before they make it into production.

{% endDetails %}

# PWAs in app stores

PWAs can be accessed through a web browser, but they can also be installed on a user's home screen as outlined in the articles in the section [Provide an installable experience](/progressive-web-apps/#provide-an-installable-experience). One of the challenges with PWAs, however, is distributing them to app stores. This is where [PWABuilder](https://pwabuilder.com/) comes in.  
PWABuilder is a powerful tool that allows developers to create packages that can be submitted to various app stores:

- [Google Play Store](https://play.google.com/store)
- [Microsoft Store](https://apps.microsoft.com/)
- [Apple App Store](https://www.apple.com/app-store/)
- [Meta Quest Store](https://www.oculus.com/experiences/quest/)

One of the major advantages of using PWABuilder to create packages is that it simplifies the process of publishing your web application to app stores. Normally, submitting an app to app stores requires a lot of work, including writing code in languages web developers may not necessarily be familiar with, creating app icons, configuring various settings, and testing the app across different devices and operating systems. PWABuilder takes care of many of these tasks automatically, reducing the amount of time and effort required to publish apps.  

{% Aside %}
Internally, PWABuilder uses a command line tool called [`bubblewrap`](https://github.com/GoogleChromeLabs/bubblewrap), which you can learn more about in the article [Trusted Web Activities Quick Start Guide](https://developer.chrome.com/docs/android/trusted-web-activity/quick-start/). Rather than use PWABuilder, you can run `bubblewrap` directly if you prefer the command line over graphical user interfaces.
{% endAside %}

## Prerequisites

If your web app meets a few baseline PWA requirements, you can use PWABuilder to validate, score, and package your application for stores.

- Your PWA needs to be published at a public URL.
- It must have a complete [web app manifest](https://developer.mozilla.org/docs/Web/Manifest).
- The app has to be served with HTTPS.

{% Aside 'important' %}
<p>To publish your package, you will also need a developer account with each platform you want to publish with. For the Microsoft and Google Play stores, these cost a one time fee. For the Apple App Store, an account costs a recurring fee per year. Meta Quest accounts are free.</p>
<p>By submitting to stores, you need to play by their rules. These rules may require you to use their payment mechanisms and pay a commission for each purchase. They may also restrict certain types of content. Carefully assess these aspects for each store you want to publish to.</p>
{% endAside %}

## Packaging

You can have an application package for your PWA in a few steps:

- From the homepage of PWABuilder, enter a URL to start the packaging process. PWABuilder will take you to the report card page for your application, where you can view scores and action items for your progressive web app.  
- To proceed to packaging your app, click the **Package for Stores** button on the top right of the scorecard page.  
- Browse the options for packaging, and select a platform by clicking **Generate Package**. You will be prompted for metadata related to your application, which will vary depending on platform.
- Lastly, select **Download Package** to download your package.

{% Img src="image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png", alt="SVGcode PWA in the PWABuilder user interface.", width="800", height="1136" %}

## Submitting

For guidance on how to publish a PWA to a specific store, be sure to check out the platform-specific articles:

- [Microsoft Store](https://docs.pwabuilder.com/#/builder/windows)
- [Google Play Store](https://docs.pwabuilder.com/#/builder/android)
- [Apple App Store](https://docs.pwabuilder.com/#/builder/app-store)
- [Meta Quest Store](https://docs.pwabuilder.com/#/builder/meta)

## Example 

I have generated store packages for one of my apps, [SVGcode](/svgcode), using PWABuilder.

<p><a href="https://play.google.com/store/apps/details?id=de.svgco.twa"><img width="200px" src="https://raw.githubusercontent.com/tomayac/SVGcode/main/public/badges/play-store.svg" alt="Get it on Google Play."></a></p>

<p><a href="https://www.microsoft.com/en-us/p/svgcode/9plhxdgsw1rj#activetab=pivot:overviewtab"><img width="200px" src="https://raw.githubusercontent.com/tomayac/SVGcode/main/public/badges/microsoft-store.svg" alt="Get it from Microsoft."></a></p>

Apart from these stores, you can of course get the app in the browser as well.

<p><a href="https://svgco.de/"><img width="200px" src="https://raw.githubusercontent.com/tomayac/SVGcode/main/public/badges/web-browser.svg" alt="Use it in your web browser."></a></p>

# Using a PWA in your Android app

[Progressive Web Apps][1] (PWA) are web applications that use app-like features to create
high quality experiences that are fast, reliable, and engaging.

The web has incredible reach and offers powerful ways for users to discover new experiences. But
users are also used to searching for applications in their operating system store. Those users are,
in many cases, already familiar with the brand or service they're seeking and have a high level of intentionality
that results in higher than average engagement metrics.

The Play Store is a store for Android apps, and developers often want to open their Progressive Web
Apps from their Android apps.

Trusted Web Activity is an open standard that allows browsers to provide a fully web platform
compatible container that renders PWAs inside an Android app. The feature
is available in [Chrome][2], and in development in [Firefox Nightly][3].

### Existing solutions were limited

It has always been possible to include web experiences in an Android app, using technologies like
the [Android WebView][4] or frameworks like [Cordova][5].

The limitation with Android WebView is that it's not intended as a browser replacement. The Android
WebView is a developer tool for using web UI in an Android app and it doesn't provide complete
access to modern web platform features such as [contact picker][6], or [filesystem][7],
[among others][17].

Cordova was designed to augment the shortcomings of WebView, but the APIs are then limited to the
Cordova environment.  That means you need to maintain an additional codebase for using Cordova APIs
for your Android app, separate from your PWA on the open web.

In addition, feature discoverability often doesn't always work as expected and compatibility issues
between Android versions and OEMs can also be a problem. When using one of those solutions,
developers need additional quality assurance processes and incur an extra development cost to
detect and create workarounds.

### Trusted Web Activity is a new container for Web apps on Android

Developers can now use a [Trusted Web Activity][8] as a container to include a PWA as a launch
activity for an Android app. The technology leverages the browser to render the PWA in full screen,
ensuring the Trusted Web Activity has the same compatibility with the Web Platform features and
APIs that the underlying browser does. There are also open source utilities to make implementing
an Android app using a Trusted Web Activity even easier.

Another advantage not available in other solutions is that the container shares storage with the
browser. Login states and users preferences are shared seamlessly across experiences.

#### Browser Compatibility

The feature has been available in Chrome since version 75, with Firefox implementing it in their
nightly version.

### Quality Criteria

Web developers should use a Trusted Web Activity when they want to include web content in an
Android app.

Web content in a Trusted Web Activity must meet the PWA installability criteria.

Additionally, to match the behavior users expect from Android applications,
[Chrome 86 introduced a change][18] where failing to handle the following scenarios is considered a
crash:

  - Failure to verify digital asset links at application launch.
  - Failure to return HTTP 200 for an offline network resource request.
  - A navigation request returning an HTTP 404 or 5xx error".

When one of those scenarios happens in the Trusted Web Activity, it causes a user visible
crash of the Android application. Check out the [guidance][19] on handling those scenarios in your
service worker.

The application must also meet additional Android-specific criteria such as [policy compliance][9].

{% Aside 'caution' %}
  When your app is designed primarily for children under 13, additional
  [Play Family policies](https://play.google.com/about/families/) apply, which may be incompatible
  with using Trusted Web Activity.
{% endAside %}

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9Z70W3aCI8ropKpMXHcz.png", alt="A screenshot showing the Lighthouse score for AirHorn, with the PWA badge and a performance score of 100.", width="800", height="141" %}
  <figcaption>
    The PWA badge in Lighthouse shows if your PWA passes the installability criteria.
  </figcaption>
</figure>

## Tooling

Web developers who want to take advantage of Trusted Web Activity don't need to learn new
technologies or APIs to transform their PWA into an Android Application. Together, Bubblewrap and
PWABuilder provide developer tooling in the form of a library, Command Line Interface (CLI) and
Graphical User Interface (GUI).

### Bubblewrap

The [Bubblewrap][10] project generates Android apps in the form of a NodeJS
library and a Command Line Interface (CLI).

Bootstrapping a new project is achieved by running the tool and passing the URL of the Web
Manifest:

```shell
npx @bubblewrap/cli init --manifest=https://pwa-directory.appspot.com/manifest.json
```

The tool can also build the project, and running the command below will output an Android
application ready to be uploaded to the Play Store:

```shell
npx @bubblewrap/cli build
```

After running this command, a file called `app-release-signed.apk` will be available in the root
directory for the project. This is the file that will be [uploaded to the Play Store][11].

### PWABuilder

[PWABuilder][12] helps developers transform existing websites into Progressive Web Apps. It also
integrates with Bubblewrap to provide a GUI interface to wrap those PWAs into an Android app.
The PWABuilder team has put together a [great blog post][13] on how to generate an Android application
using the tool.

## Verifying ownership of the PWA in the Android app

A developer building a great Progressive Web App wouldn't want another developer to build an
Android app with it without their permission. To ensure this doesn't happen, the Android
application must be paired with the Progressive Web App using a tool called
[Digital Asset Links][14].

Bubblewrap and PWABuilder take care of the necessary configuration on the Android application, but
a last step remains, which is adding the `assetlinks.json` file to the PWA.

To generate this file, developers need the SHA-256 signature of the key used to sign the APK that
is being downloaded by the users.

The key can be generated in multiple ways, and the easiest way to find which key that signed the
APK being served to end users is to download it from the Play Store itself.

To avoid showing a broken application to users, deploy the application to a
[closed test channel][11], install it into a test device then use [Peter's Asset Link Tool][15] to
generate the correct `assetlinks.json` file for the app. Make the generated `assetlinks.json` file
available at `/.well-known/assetlinks.json`, in the domain being validated.

## Where to go next

A Progressive Web App is a high quality web experience. Trusted Web Activity is a new way to open
those high quality experiences from an Android app when they meet the minimum quality criteria.

If you are getting started with Progressive Web Apps, read
[our guidance on how to build a great PWA][1]. For developers who already have a PWA, use
[Lighthouse][16] to verify if it meets the quality criteria.

Then, use [Bubblewrap][10] or [PWABuilder][12] to generate the Android application,
[upload the application to a closed test channel on the Play Store][11] and pair it with the PWA
using [Peter's Asset Link Tool][15].

Finally, move your application from the closed test channel to production!

[1]: /progressive-web-apps/
[2]: https://play.google.com/store/apps/details?id=com.android.chrome
[3]: https://play.google.com/store/apps/details?id=org.mozilla.fenix
[4]: https://developer.android.com/reference/android/webkit/WebView
[5]: https://cordova.apache.org/
[6]: /contact-picker/
[7]: /file-system-access/
[8]: https://developers.google.com/web/updates/2019/02/using-twa
[9]: https://play.google.com/about/developer-content-policy/
[10]: https://github.com/GoogleChromeLabs/bubblewrap
[11]: https://support.google.com/googleplay/android-developer/answer/3131213?hl=en-GB
[12]: https://pwabuilder.com/
[13]: https://www.davrous.com/2020/02/07/publishing-your-pwa-in-the-play-store-in-a-couple-of-minutes-using-pwa-builder/
[14]: https://developers.google.com/digital-asset-links/v1/getting-started
[15]: https://play.google.com/store/apps/details?id=dev.conn.assetlinkstool
[16]: https://developer.chrome.com/docs/lighthouse/overview/
[17]: /fugu-status/
[18]: https://blog.chromium.org/2020/06/changes-to-quality-criteria-for-pwas.html
[19]: https://developer.chrome.com/docs/android/trusted-web-activity/whats-new/#updates-to-the-quality-criteria

# What are Progressive Web Apps?

The web is an incredible platform. Its mix of ubiquity across devices and
operating systems, its user-centered security model, and the fact that neither
its specification nor its implementation are controlled by a single company makes
the web a unique platform to develop software on. Combined with its inherent
linkability, it's possible to search it and share what you've found with
anyone, anywhere. Whenever you go to a website, it's up-to-date, and your
experience with that site can be as ephemeral or as permanent as you'd like. Web
applications can reach _anyone, anywhere, on any device_ with a single codebase.

Platform-specific applications are known for being incredibly rich and reliable. They're
ever-present, on home screens, docks, and taskbars. They work regardless of
network connection. They launch in their own standalone experience. They can
read and write files from the local file system, access hardware connected via
USB, serial port, or bluetooth, and even interact with data stored on your device,
such as contacts and calendar events. In these applications, you can do things
such as take pictures, see playing songs listed on the home screen, or control song
playback while in another app. Platform-specific applications feel like _part_ of the
device they run on.

<figure>
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1DKtUFjXLJbiiruKA9P1.svg", alt="A graph illustrating the relative capabilites and reach of platform-specific apps, with high capabilities, web apps, with high reach, and progressive web apps, which have both high capabilities and high reach.", width="370", height="367" %}
  <figcaption>
    Capabilities vs. reach of platform-specific apps, web apps, and progressive web apps.
  </figcaption>
</figure>

If you think about platform-specific apps and web apps in terms of capabilities and reach,
platform-specific apps represent the best of capabilities whereas web apps represent the
best of reach. So where do Progressive Web Apps fit in?

Progressive Web Apps (PWA) are built and enhanced with modern APIs to deliver
enhanced capabilities, reliability, and installability while reaching
_anyone, anywhere, on any device_ with a single codebase.

## The three app pillars

Progressive Web Apps are web applications that have been designed to be
capable, reliable, and installable. These three pillars transform them into an
experience that feels like a platform-specific application.

### Capable

The web is quite capable in its own right today. For example, you can build a hyper-local
video chat app using WebRTC, geolocation, and push notifications. You can make
that app installable and take its conversations virtual with WebGL and WebVR.
With the introduction of WebAssembly, developers can tap into other ecosystems,
such as C, C++, and Rust, and bring decades of work and capabilities to the web.
[Squoosh.app](https://squoosh.app/), for instance, uses this for its
advanced image compression.

Until recently, only platform-specific apps could really lay claim to these capabilities.
While some capabilities are still out of the web's reach, new and upcoming APIs
are looking to change that, expanding what the web can do with features like
file system access, media controls, app badging, and full clipboard support. All
of these capabilities are built with the web's secure, user-centric permission
model, ensuring that going to a website is never scary for users.

Between modern APIs, WebAssembly, and new and upcoming APIs, web applications
are more capable than ever, and those capabilities are only growing.

### Reliable

A reliable Progressive Web App feels fast and dependable regardless of the
network.

Speed is critical for getting users to _use_ your experience. In fact, as page
load times go from 1 second to ten seconds, the probability of a user bouncing
[increases by 123%](https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/).
Performance doesn't stop after the `onload` event. Users should never wonder whether their
interaction—for example, clicking a button—was registered or not. Scrolling and
animation should feel smooth. Performance affects the entire user experience, from
how they perceive your application to how it actually performs.

Finally, reliable applications need to be usable regardless of network
connection. Users expect apps to start up on slow or flaky network connections
or even when offline. They expect the most recent content they've interacted
with, such as media tracks or tickets and itineraries, to be available and usable
even if getting a request to your server is hard. When a request isn't possible,
they expect to be told there's trouble instead of silently failing or crashing.

Users deserve apps that respond to interaction in the blink of an eye, and an
experience they can depend on.

### Installable

Installed Progressive Web Apps run in a standalone window instead of a browser
tab. They're launchable from on the user's home screen, dock, taskbar, or shelf.
It's possible to search for them on a device and jump between them with the app
switcher, making them feel like part of the device they're installed on.

New capabilities open up after a web app is installed. Keyboard shortcuts, usually
reserved when running in the browser, become available. Progressive Web
Apps can register to accept content from other applications, or to be the
default application to handle different types of files.

When a Progressive Web App moves out of a tab and into a standalone app window,
it transforms how users think about it and interact with it.

## The best of both worlds

At their heart, Progressive Web Apps are just web applications. Using
progressive enhancement, new capabilities are enabled in modern browsers. Using
service workers and a web app manifest, your web application becomes reliable
and installable. If the new capabilities aren't available, users still get the core experience.

The numbers don't lie! Companies that have launched Progressive Web Apps have
seen impressive results. For example, Twitter saw a 65% increase in pages per
session, 75% more Tweets, and a 20% decrease in bounce rate, all while reducing
the size of their app by over 97%. After switching to a PWA, Nikkei saw 2.3
times more organic traffic, 58% more subscriptions, and 49% more daily active
users. Hulu replaced their platform-specific desktop experience with a Progressive Web App
and saw a 27% increase in return visits.

Progressive Web Apps provide you with a unique opportunity to deliver a web
experience your users will love. Using the latest web features to bring
enhanced capabilities and reliability, Progressive Web Apps allow what you
build to be installed by _anyone, anywhere, on any device_ with a single
codebase.

<!--lint disable no-unescaped-template-tags-->
{% from 'macros/prerender.njk' import prerender with context %}
{# Prerender /progressive-web-apps/ as a test of prerendering #}
{{ prerender('/progressive-web-apps/') }}
<!--lint enable no-unescaped-template-tags-->