Installing and uninstalling web apps - Progressive web apps

Installing and uninstalling web apps
====================================

This guide covers how users can install and uninstall PWAs on their devices. If you want to learn about making a web app installable as a PWA, see Making PWAs installable instead.

History of web app installation
-------------------------------

Browsers have always enabled saving shortcuts to websites, known as "bookmarking." These are just links to websites.

Some operating systems (OS) have enhanced bookmarking capabilities, enabling saving bookmarks to common places, such as the home screen or taskbar, with an icon launching the site in the OS's default browser. For many websites, this is also just a link to the site. If the site is a Progressive Web Applications (PWA), saving to home screen installs the PWA on the user's device, fully integrating it into the operating systems like native applications on most devices. Just like PWAs can be installed, they can also be uninstalled.

We'll first cover the precursors — saving links to websites.

### Bookmarking websites

All browsers have add-to-favorites bookmark functionality. A bookmark, or favorite, is a clickable shortcut for a web page. Bookmarks enable quick access to websites without the user having to enter a URL or otherwise search for content. Bookmarking is especially useful for long URLs and accessing frequently visited content that is not the site's homepage.

All browsers enable users to view and manage their bookmarks, including renaming and deleting favorites. By default, the bookmark's display includes the text content of the bookmarked page's `<title>` element along with an icon consisting of the site's favicon.

Browsers enable saving, editing, moving, deleting, and otherwise managing bookmarks. The UI for bookmark management differs by browser.

### Add to home screen

Smartphones, starting with the iPhone in 2007, added "save to home screen" functionality. For regular (non-PWA) websites, this feature is similar to bookmarks, but instead of adding the favicon and title of the page to the bookmarks menu — a browser feature — favoriting in this manner adds an icon to the OS's home screen.

Adding a non-PWA site to the home screen does not install the website on the device. Rather, it adds the developer-defined icon to the home screen, that, when clicked, opens the bookmarked link in the default browser.

![iPhone add to home screen, install prompt, icon, and delete functionality.](/en-US/docs/Web/Progressive_web_apps/Guides/Installing/iphone_pwa.jpg)

If the site added to the home screen is a PWA, the PWA will be installed on the device.

Deleting the icon from the home screen removes the bookmark. The deletion confirmation provides information as to whether deleting the icon deletes a bookmark or entire application.

Installing and uninstalling PWAs
--------------------------------

While installing a PWA only takes a couple of clicks, depending on the web application features, the result of installing a PWA usually goes well beyond creating a link to a page on the Internet; installing a PWA more deeply integrates the web application on the user's device.

Depending on the PWA, device, and features of the operating system and browser, installing a PWA may enable native-like features, such as giving the app its own standalone window or registering it as a file handler. This also means uninstalling a PWA, which also only requires a couple of clicks, does more than just removing the PWA's icon.

### Installing PWAs

The UI for installing a PWA from the web varies from one browser to another, and from one platform to another.

The user interface for installing PWAs differs by device and OS combination. The "Add to homes screen" user interface installs the PWA on Safari on iOS. Other browsers, including Chrome for Android, include the app installation command in the browser setting menu. In Chrome and Edge on desktop, when the user navigates to the page, if the page is a PWA and the PWA is not currently installed by the browser, an installation icon will be visible in the URL bar:

![PWA install prompt in URL bar](/en-US/docs/Web/Progressive_web_apps/Guides/Installing/pwa-install.png)

When the user selects the icon, the browser displays a prompt asking if they want to install the PWA, and if they accept, the PWA is installed.

![PWA installation confirmation prompt](/en-US/docs/Web/Progressive_web_apps/Guides/Installing/installconfirm.jpg)

Once installed, the PWA will behave like other applications that are installed on the OS. For example, on macOS, the icon will appear in the dock, and will have the same icon options as other applications:

![PWA icon in the doc on MacOS](/en-US/docs/Web/Progressive_web_apps/Guides/Installing/dock.jpg)

On most desktop browsers, the install prompt is in the URL bar. On mobile, the install prompt is generally found in the menu of browser options. No matter the browser or OS, the installation needs to be confirmed.

![PWA installation on Chrome for Android, with confirmation, home screen icon, and offline experience.](/en-US/docs/Web/Progressive_web_apps/Guides/Installing/android_pwa.jpg)

Once installed, the PWA behaves just like other installed applications: clicking on the application icon opens the PWA, even when the user is offline.

Installation is supported on all modern desktop and mobile devices. Whether the PWA can be installed by the browser on the operating system differs by browser/operating system combination. Most browsers support installing PWAs on all operating systems—ChromeOS, MacOS, Windows, Android, Linux, etc.—directly or when an extension is installed.

Firefox requires a PWA extension.

Apple is unique when it comes to PWAs: PWAs can be installed on macOS from any browser **except** Safari. The opposite is true for iOS versions before 16.4, where PWAs could **only** be installed in Safari. PWAs can be installed on iOS/iPadOS 16.4 or later from any supporting browser.

When an installed PWA is launched, it can be displayed in its own standalone window (without the full browser UI) but it still effectively runs in a browser window, even if the usual browser UI elements, such as the address bar or back button, aren't visible. The application will be found where the OS saves other applications, within a folder specific to the browser.

PWAs installed by a browser remain specific to this browser. This means that the browser that was used to install a PWA is the one used to run that PWA. It also means that you can install the same PWA from a different browser and that the two apps will behave as two different instances and will not share any data.

The browser used to install the PWA will know the PWA is installed, but other browsers will not have access to the installed status. For example, if you install a PWA using MS Edge, Edge will prompt you to open the PWA when you visit the site while Chrome will continue to prompt you to install the application. If you install the PWA using Chrome as well, you will have two copies of the PWA. When multiple instances of a PWA are open, data is not shared between instances installed from different browsers.

When you tap the web app's icon, it opens up in the browser environment that installed the PWA, generally without the browser's UI around it, though that depends on the way the developer configured the web app manifest. Similarly, the method used to uninstall the PWA depends on the browser that was used to install it.

### Uninstalling

On most mobile operating systems, uninstalling a PWA is accomplished in the same way as uninstalling other applications. On some mobile operating systems, PWAs appear in the same control panel where applications downloaded from app stores are managed and can be uninstalled there.

On iOS, PWAs installed from Safari are listed and searchable from the "App Library" screen, but are not listed along with other installed applications under "Settings". On iOS, long tapping an icon surfaces the delete bookmark UI; removing the icon from the home screen deletes the PWA.

In some desktop operating systems, uninstalling a PWA can be done directly in opened PWA. To uninstall, open the PWA. In the top right corner of the opened app, there will be an icon that must be expanded to see more tools. Depending on the browser used to install the PWA, there will either be a link to uninstall the PWA, or a settings link that opens the browser's settings page with an uninstall link. Either click on the uninstall option in the drop-down menu, if there, or navigate to the app settings in a browser tab and click uninstall.

![App settings in MS Edge with an uninstall link](/en-US/docs/Web/Progressive_web_apps/Guides/Installing/remove.jpg)

Selecting app setting from the opened drop-down menu in Edge, opened the MS Edge browser `edge://apps` tab. There we are provided a list of installed applications with options for each, including `🗑 Uninstall`. Confirm the uninstall. That's it!

In Edge, the installed PWAs are listed and can be managed by visiting `edge://apps` in your Edge browser. In Chrome, the list of Google Apps and installed PWAs are viewable and managed by visiting `chrome://apps` in your Chrome browser.

See also
--------

* Using PWAs in Chrome: computer and Android
* Install, manage, or uninstall apps in Microsoft Edge


Offline and background operation - Progressive web apps

Offline and background operation
================================

Usually, websites are very dependent on both reliable network connectivity and on the user having their pages open in a browser. Without network connectivity, most websites are just unusable, and if the user does not have the site open in a browser tab, most websites are unable to do anything.

However, consider the following scenarios:

* A music app enables the user to stream music while online, but can download tracks in the background and then continue to play while the user is offline.
* The user composes a long email, presses "Send", and then loses network connectivity. The device sends the email in the background, as soon as the network is available again.
* The user's chat app receives a message from one of their contacts, and although the app is not open, it displays a badge on the app icon to let the user know they have a new message.

These are the kinds of features that users expect from installed apps. In this guide, we'll introduce a set of technologies that enable a PWA to:

* Provide a good user experience even when the device has intermittent network connectivity
* Update its state when the app is not running
* Notify the user about important events that have happened while the app was not running

The technologies introduced in this guide are:

* Service Worker API
* Background Synchronization API
* Background Fetch API
* Periodic Background Synchronization API
* Push API
* Notifications API
Websites and workers
--------------------

The foundation of all the technologies we'll discuss in this guide is the *service worker*. In this section we'll provide a little background about workers and how they change the architecture of a web app.

Normally, an entire website runs in a single thread. This includes the website's own JavaScript, and all the work to render the website's UI. One consequence of this is that if your JavaScript runs some long-running operation, the website's main UI is blocked, and the website appears unresponsive to the user.

A service worker is a specific type of web worker that's used to implement PWAs. Like all web workers, a service worker runs in a separate thread to the main JavaScript code. The main code creates the worker, passing in a URL to the worker's script. The worker and the main code can't directly access each other's state, but can communicate by sending each other messages. Workers can be used to run computationally expensive tasks in the background: because they run in a separate thread, the main JavaScript code in the app, that implements the app's UI, can stay responsive to the user.

So a PWA always has a high level architecture split between:

* The *main app*, with the HTML, CSS, and the part of the JavaScript that implements the app's UI (by handling user events, for example)
* The *service worker*, which handles offline and background tasks

In this guide, when we show code samples, we'll indicate which part of the app the code belongs in with a comment like `// main.js` or `// service-worker.js`.

Offline operation
-----------------

Offline operation allows a PWA to provide a good user experience even when the device does not have network connectivity. This is enabled by adding a service worker to an app.

A service worker *controls* some or all of the app's pages. When the service worker is installed, it can fetch the resources from the server for the pages it controls (including pages, styles, scripts, and images, for example) and add them to a local cache. The `Cache` interface is used to add resources to the cache. `Cache` instances are accessible through the `caches` property in the service worker global scope.

Then whenever the app requests a resource (for example, because the user opened the app or clicked an internal link), the browser fires an event called `fetch` in the service worker's global scope. By listening for this event, the service worker can intercept the request.

The event handler for the `fetch` event is passed a `FetchEvent` object, which:

* Provides access to the request as a `Request` instance
* Provides a `respondWith()` method to send a response to the request.

One way a service worker can handle requests is a "cache-first" strategy. In this strategy:

1. If the requested resource exists in the cache, get the resource from the cache and return the resource to the app.
2. If the requested resource does not exist in the cache, try to fetch the resource from the network.
    1. If the resource could be fetched, add the resource to the cache for next time, and return the resource to the app.
    2. If the resource could not be fetched, return some default fallback resource.

The following code sample shows an implementation of this:

```js
// service-worker.js

const putInCache = async (request, response) => {
  const cache = await caches.open("v1");
  await cache.put(request, response);
};

const cacheFirst = async ({ request, fallbackUrl }) => {
  // First try to get the resource from the cache.
  const responseFromCache = await caches.match(request);
  if (responseFromCache) {
    return responseFromCache;
  }

  // If the response was not found in the cache,
  // try to get the resource from the network.
  try {
    const responseFromNetwork = await fetch(request);
    // If the network request succeeded, clone the response:
    // - put one copy in the cache, for the next time
    // - return the original to the app
    // Cloning is needed because a response can only be consumed once.
    putInCache(request, responseFromNetwork.clone());
    return responseFromNetwork;
  } catch (error) {
    // If the network request failed,
    // get the fallback response from the cache.
    const fallbackResponse = await caches.match(fallbackUrl);
    if (fallbackResponse) {
      return fallbackResponse;
    }
    // When even the fallback response is not available,
    // there is nothing we can do, but we must always
    // return a Response object.
    return new Response("Network error happened", {
      status: 408,
      headers: { "Content-Type": "text/plain" },
    });
  }
};

self.addEventListener("fetch", (event) => {
  event.respondWith(
    cacheFirst({
      request: event.request,
      fallbackUrl: "/fallback.html",
    }),
  );
});
```

This means that in many situations, the web app will function well even if network connectivity is intermittent. From the point of view of the main app code, it is completely transparent: the app just makes network requests and gets responses. Also, because the service worker is in a separate thread, the main app code can stay responsive to user input while resources are fetched and cached.

**Note:** The strategy described here is just one way a service worker could implement caching. Specifically, in a cache first strategy, we check the cache first before the network, meaning that we are more likely to return a quick response without incurring a network cost, but are more likely to return a stale response.

An alternative would be a *network first* strategy, in which we try to fetch the resource from the server first, and fall back to the cache if the device is offline.

The optimal caching strategy is dependent on the particular web app and how it is used.

For much more detail about setting up service workers and using them to add offline functionality, see our guide to using service workers.

Background operation
--------------------

While offline operations are the most common use for service workers, they also enable a PWA to operate even while the main app is closed. This is possible because the service worker can run while the main app is not running.

This doesn't mean service workers run all the time: browsers may stop service workers when they think it is appropriate. For example, if a service worker has been inactive for a while, it will be stopped. However, the browser will restart the service worker when an event has happened that it needs to take care of. This enables a PWA to implement background operations in the following way:

* In the main app, register a request for the service worker to perform some operation
* At the appropriate time, the service worker will be restarted if necessary, and an event will fire in the service worker's scope
* The service worker will perform the operation

In the next sections, we'll discuss a few different features that use this pattern to enable a PWA to work while the main app isn't open.

Background sync
---------------

Suppose a user composes an email and presses "Send". In a traditional website, they must keep the tab open until the app has sent the email: if they close the tab, or the device loses connectivity, then the message will not be sent. Background sync, defined in the Background Synchronization API, is the solution to this problem for PWAs.

Background sync enables the app to ask its service worker to perform a task on its behalf. As soon as the device has network connectivity, the browser will restart the service worker, if necessary, and fire an event named `sync` in the service worker's scope. The service worker can then attempt to execute the task. If the task can't be completed, then the browser may retry a limited number of times by firing the event again.

### Registering a sync event

To ask the service worker to perform a task, the main app can access `navigator.serviceWorker.ready`, which resolves with a `ServiceWorkerRegistration` object. The app then calls `sync.register()` on the `ServiceWorkerRegistration` object, like this:

```js
// main.js

async function registerSync() {
  const swRegistration = await navigator.serviceWorker.ready;
  swRegistration.sync.register("send-message");
}
```

Note that the app passes a name for the task: `"send-message"` in this case.

### Handling a sync event

As soon as the device has network connectivity, the `sync` event fires in the service worker scope. The service worker checks the name of the task and runs the appropriate function, `sendMessage()` in this case:

```js
// service-worker.js

self.addEventListener("sync", (event) => {
  if (event.tag == "send-message") {
    event.waitUntil(sendMessage());
  }
});
```

Note that we pass the result of the `sendMessage()` function into the event's `waitUntil()` method. The `waitUntil()` method takes a `Promise` as a parameter and asks the browser not to stop the service worker until the promise has settled. This is also how the browser knows whether the operation succeeded or not: if the promise rejects, then the browser may retry by firing the `sync` event again.

The `waitUntil()` method is not a guarantee that the browser will not stop the service worker: if the operation takes too long, the service worker will be stopped anyway. If this happens, then the operation is aborted, and next time a `sync` event is fired, then the handler runs again from the start - it does not resume from where it left off.

How long is "too long" is browser-specific. For Chrome, the service worker is likely to be closed if:

* It has been idle for 30 seconds
* It has been running synchronous JavaScript for 30 seconds
* The promise passed to `waitUntil()` has taken more than 5 minutes to settle
Background fetch
----------------

Background sync is useful for relatively short background operations, but as we just saw: if a service worker doesn't finish handling a sync event in a relatively short time, the browser will stop the service worker. This is an intentional measure to conserve battery life and protect the user's privacy by minimizing the time for which the user's IP address is exposed to the server while the app is in the background.

This makes background sync unsuitable for longer operations - downloading a movie, for example. For this scenario, you need the Background Fetch API. With background fetch, network requests can be performed while both the main app UI and the service worker are closed.

With background fetch:

* The request is initiated from the main app UI
* Whether or not the main app is open, the browser displays a persistent UI element that notifies the user about the ongoing request, and enables them to cancel it or check its progress
* When the request is completed with success or failure, or the user has asked to check the request's progress, then the browser starts the service worker (if necessary) and fires the appropriate event in the service worker's scope.
### Making a background fetch request

A background fetch request is initiated in the main app code, by calling `backgroundFetch.fetch()` on the `ServiceWorkerRegistration` object, like this:

```js
// main.js

async function requestBackgroundFetch(movieData) {
  const swRegistration = await navigator.serviceWorker.ready;
  const fetchRegistration = await swRegistration.backgroundFetch.fetch(
    "download-movie",
    ["/my-movie-part-1.webm", "/my-movie-part-2.webm"],
    {
      icons: movieIcons,
      title: "Downloading my movie",
      downloadTotal: 60 \* 1024 \* 1024,
    },
  );
  //...
}
```

We're passing three arguments into `backgroundFetch.fetch()`:

1. An identifier for this fetch request
2. An array of `Request` objects or URLs. A single background fetch request can include multiple network requests.
3. An object containing data for the UI that the browser uses to show the existence and progress of the request.

The `backgroundFetch.fetch()` call returns a `Promise` that resolves to a `BackgroundFetchRegistration` object. This enables the main app to update its own UI as the request progresses. However, if the main app is closed, the fetch will continue in the background.

The browser will display a persistent UI element reminding the user that the request is ongoing, giving them the chance to find out more about the request and cancel it if they wish. The UI will include an icon and title taken from the `icons` and `title` arguments, and uses `downloadTotal` as an estimate of the total download size, to show the request's progress.

### Handling request outcomes

When the fetch has finished with success or failure, or the user has clicked the progress UI, then the browser starts the app's service worker, if necessary, and fires an event in the service worker's scope. The following events can be fired:

* `backgroundfetchsuccess`: all requests were successful
* `backgroundfetchfail`: at least one request failed
* `backgroundfetchabort`: the fetch was canceled by the user or by the main app
* `backgroundfetchclick`: the user clicked on the progress UI element that the browser is showing

#### Retrieving response data

In the handlers for the `backgroundfetchsuccess`, `backgroundfetchfail`, and `backgroundfetchabort` events, the service worker can retrieve the request and response data.

To get the response, the event handler accesses the event's `registration` property. This is a `BackgroundFetchRegistration` object, which has `matchAll()` and `match()` methods that return `BackgroundFetchRecord` objects matching the given URL (or, in the case of `matchAll()` all records if no URL is given).

Each `BackgroundFetchRecord` has a `responseReady` property that is a `Promise` which resolves with the `Response`, once the response is available.

So to access response data, the handler could do something like:

```js
// service-worker.js

self.addEventListener("backgroundfetchsuccess", (event) => {
  const registration = event.registration;

  event.waitUntil(async () => {
    const registration = event.registration;
    const records = await registration.matchAll();
    const responsePromises = records.map(
      async (record) => await record.responseReady,
    );

    const responses = Promise.all(responsePromises);
    // do something with the responses
  });
});
```

Since the response data won't be available after the handler exits, the handler should store the data (for example, in the `Cache`) if the app still wants it.

#### Updating the browser's UI

The event object passed into `backgroundfetchsuccess` and `backgroundfetchfail` also has an `updateUI()` method, which can be used to update the UI that the browser shows to keep the user informed about the fetch operation. With `updateUI()`, the handler can update the UI element's title and icon:

```js
// service-worker.js

self.addEventListener("backgroundfetchsuccess", (event) => {
  // retrieve and store response data
  // ...

  event.updateUI({ title: "Finished your download!" });
});

self.addEventListener("backgroundfetchfail", (event) => {
  event.updateUI({ title: "Could not complete download" });
});
```

#### Responding to user interaction

The `backgroundfetchclick` event is fired when the user has clicked on the UI element that the browser shows while the fetch is ongoing.

The expected response here is to open a window giving the user more information about the fetch operation, which can be done from the service worker using `clients.openWindow()`. For example:

```js
// service-worker.js

self.addEventListener("backgroundfetchclick", (event) => {
  const registration = event.registration;

  if (registration.result === "success") {
    clients.openWindow("/play-movie");
  } else {
    clients.openWindow("/movie-download-progress");
  }
});
```

Periodic background sync
------------------------

The Periodic Background Synchronization API enables a PWA to periodically update its data in the background, while the main app is closed.

This can greatly improve the offline experience offered by a PWA. Consider an app that depends on reasonably fresh content, like a news app. If the device is offline when the user opens the app, then even with service worker-based caching the stories will only be as fresh as the last time the app was opened. With periodic background sync, the app could have refreshed its stories in the background, when the device had connectivity, and so could be able to show relatively fresh content to the user.

This takes advantage of the fact that on a mobile device especially, connectivity is not poor so much as *intermittent*: by taking advantage of the times that the device has connectivity, the app can smooth over the connectivity gaps.

### Registering a periodic sync event

The code for registering a periodic sync event follows the same pattern as that for registering a sync event. The `ServiceWorkerRegistration` has a `periodicSync` property, which has a `register()` method taking the name of the periodic sync as a parameter.

However, `periodicSync.register()` takes an extra argument, which is an object with a `minInterval` property. This represents the minimum interval, in milliseconds, between synchronization attempts:

```js
// main.js

async function registerPeriodicSync() {
  const swRegistration = await navigator.serviceWorker.ready;
  swRegistration.periodicSync.register("update-news", {
    // try to update every 24 hours
    minInterval: 24 \* 60 \* 60 \* 1000,
  });
}
```

### Handling a periodic sync event

Although the PWA asks for a particular interval in the `register()` call, it's up to the browser how often to generate periodic sync events. Apps that users open and interact with often will be more likely to receive periodic sync events, and will receive them more often, than apps which the user rarely or never interacts with.

When the browser has decided to generate a periodic sync event, the pattern is the following: it starts the service worker, if necessary, and fires a `periodicSync` event in the service worker's global scope.

The service worker's event handler checks the name of the event, and calls the appropriate function inside the event's `waitUntil()` method:

```js
// service-worker.js

self.addEventListener("periodicsync", (event) => {
  if (event.tag === "update-news") {
    event.waitUntil(updateNews());
  }
});
```

Inside `updateNews()`, the service worker can fetch and cache the latest stories. The `updateNews()` function should complete relatively quickly: if the service worker takes too long updating its content, the browser will stop it.

### Unregistering a periodic sync

When the PWA no longer needs periodic background updates, (for example, because the user has switched them off in the app's settings) then the PWA should ask the browser to stop generating periodic sync events, by calling the `unregister()` method of `periodicSync`:

```js
// main.js

async function registerPeriodicSync() {
  const swRegistration = await navigator.serviceWorker.ready;
  swRegistration.periodicSync.unregister("update-news");
}
```

Push
----

The Push API enables a PWA to receive messages pushed from the server, whether the app is running or not. When the message is received by the device, the app's service worker is started and handles the message, and a notification is shown to the user. The specification allows for "silent push" in which no notification is shown, but no browsers support this, because of privacy concerns (for example, that push could then be used to track a user's location).

Displaying a notification to the user distracts them from whatever they were doing and has the potential to be very annoying, so use push messages with care. In general, they are suitable for situations in which you need to alert the user about something, and can't wait until the next time they open your app.

A common use case for push notifications is chat apps: when the user receives a message from one of their contacts, it is delivered as a push message and the app shows a notification.

Push messages are not sent directly from the app server to the device. Instead, your app server sends messages to a push service, from which the device can retrieve them and deliver them to the app.

This also means that messages from your server to the push service need to be encrypted (so the push service can't read them) and signed (so the push service knows that the messages are really from your server, and not from someone impersonating your server).

The push service is operated by the browser vendor or by a third party, and the app server communicates with it using the HTTP Push protocol. The app server can use a third-party library such as web-push to take care of the protocol details.

### Subscribing to push messages

The pattern for subscribing to push messages looks like this:

![Diagram showing push message subscription steps](/en-US/docs/Web/Progressive_web_apps/Guides/Offline_and_background_operation/push-messaging-1.svg)

1. As a prerequisite, the app server needs to be provisioned with a public/private key pair, so it can sign push messages. Signing messages needs to follow the VAPID specification.
2. On the device, the app uses the `PushManager.subscribe()` method to subscribe to messages from the server. The `subscribe()` method:
    * Takes the app server's public key as an argument: this is what the push service will use to verify the signature on messages from the app server.
    * Returns a `Promise` that resolves to a `PushSubscription` object. This object includes:
        + The endpoint for the push service: this is how the app server knows where to send push messages.
        + The public encryption key that your server will use to encrypt messages to the push service.
3. The app sends the endpoint and public encryption key to your server (for example, using `fetch()`).

After this, the app server is able to start sending push messages.

### Sending, delivering, and handling push messages

When an event happens on the server that the server wants the app to handle, the server can send messages, and the sequence of steps is like this:

![Diagram showing push message sending and delivery steps](/en-US/docs/Web/Progressive_web_apps/Guides/Offline_and_background_operation/push-messaging-2.svg)

1. The app server signs the message using its private signing key and encrypts the message using the public encryption key for the push service. The app server can use a library such as web-push to simplify this.
2. The app server sends the message to the endpoint for the push service, using the HTTP Push protocol, and again optionally using a library, like web-push.
3. The push service checks the signature on the message, and if the signature is valid, the push service queues the message for delivery.
4. When the device has network connectivity, the push service delivers the encrypted message to the browser.
5. When the browser receives the encrypted message, it decrypts the message.
6. The browser starts the service worker if necessary, and fires an event called `push` in the service worker's global scope. The event handler is passed a `PushEvent` object, which contains the message data.
7. In its event handler, the service worker does any processing of the message. As usual, the event handler calls `event.waitUntil()` to ask the browser to keep the service worker running.
8. In its event handler, the service worker creates a notification using `registration.showNotification()`.
9. If the user clicks the notification or closes it, the `notificationclick` and `notificationclose`, respectively, are fired in the service worker's global scope. These enable the app to handle the user's response to the notification.
Permissions and restrictions
----------------------------

Browsers have to find a balance in which they can provide powerful APIs to web developers while protecting users from malicious, exploitative, or poorly-written websites. One of the main protections they offer is that users can close the website's pages, and it's then not active on their device any more. The APIs described in this article tend to violate that assurance, so browsers have to take extra steps to help ensure that users are aware of this, and that the APIs are used in ways that align with the interests of users.

In this section we'll outline these steps. Several of these APIs require explicit user permission, and various other restrictions and design choices to help protect users.

* The Background Sync API does not need an explicit user permission, but issuing a background sync request may only be made while the main app is open, and browsers limit the number of retries and the length of time background sync operations can take.
* The Background Fetch API requires the `"background-fetch"` user permission, and the browser displays the ongoing progress of the fetch operation, enabling the user to cancel it.
* The Periodic Background Sync API requires the `"periodic-background-sync"` user permission, and browsers should allow users to disable periodic background sync completely. Also, browsers may tie the frequency of sync events to the extent to which the user chooses to interact with the app: so an app that the user rarely uses may receive few events (or even no events at all).
* The Push API requires the `"push"` user permission, and all browsers require push events to be user visible, meaning that they generate a user-visible notification.
See also
--------

### Reference

* Service Worker API
* Background Synchronization API
* Background Fetch API
* Periodic Background Synchronization API
* Push API
* Notifications API
### Guides

* Introducing Background Sync on developer.chrome.com (2017)
* Introducing Background Fetch on developer.chrome.com (2022)
* The Periodic Background Sync API on developer.chrome.com (2020)
* Notifications on web.dev
* PWA with offline streaming on web.dev (2021)


Making PWAs installable - Progressive web apps

Making PWAs installable
=======================

One of the defining aspects of a PWA is that it can be promoted by the browser for installation on the device. Once installed, a PWA appears to users as a platform-specific app, a permanent feature of their device which they can launch directly from the operating system like any other app.

We can summarize this as follows:

* Supporting browsers promote the PWA to the user for installation on the device.
* The PWA can be installed like a platform-specific app, and can customize the install process.
* Once installed, the PWA gets an app icon on the device, alongside platform-specific apps.
* Once installed, the PWA can be launched as a standalone app, rather than a website in a browser.

We'll discuss each of these features in this guide. First, though, we'll discuss the requirements that a web app must meet for it to be promoted for installation.

Installability
--------------

For a web app to be promoted for installation by a supporting browser, it needs to meet some technical requirements. We can consider these the minimum requirements for a web app to be a PWA.

### The web app manifest

A web app manifest is a JSON file that tells the browser how the PWA should appear and behave on the device. For a web app to be a PWA it must be installable, and for it to be installable it must include a manifest.

The manifest is included using a `<link>` element in the app's HTML:

```html
<!doctype html>
<html lang="en">
  <head>
    <link rel="manifest" href="manifest.json" />
    <!-- ... -->
  </head>
  <body></body>
</html>
```

If the PWA has more than one page, every page must reference the manifest in this way.

The manifest contains a single JSON object containing a collection of members, each of which defines some aspect of the PWA's appearance or behavior. Here's a rather minimal manifest, containing just two members: `"name"` and `"icons"`.

```json
{
  "name": "My PWA",
  "icons": [
    {
      "src": "icons/512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ]
}
```

#### Required manifest members

Chromium-based browsers, including Google Chrome, Samsung Internet, and Microsoft Edge, require that the manifest includes the following members:

* `name`
* `icons`
* `start_url`
* `display` and/or `display_override`

For a full description of every member, see the web app manifest reference documentation.

### Secure context

For a web app to be installable, it must be served in a secure context. This usually means that it must be served over HTTPS. Local resources, such as localhost, `127.0.0.1` and `file://` are also considered secure.

### Service worker

For a web app to be installable, it must include a service worker with a `fetch` event handler that provides a basic offline experience.

Installation from an app store
------------------------------

Users expect to find apps in the app store for their platform, like the Google Play Store or the Apple App Store.

If your app meets the installability prerequisites, you can package it and distribute it through app stores. The process is specific to each app store:

* How to publish a PWA to the Google Play Store
* How to publish a PWA to the Microsoft Store
* How to publish a PWA to the Meta Quest Store

The PWABuilder is a tool to simplify the process of packaging and publishing a PWA for various app stores. It supports the Google Play Store, Microsoft Store, Meta Quest Store, and iOS App Store.

If you have added your app to the app store, users can install it from there, just like a platform-specific app.

Installation from the web
-------------------------

When a supporting browser determines that a web app meets the installability criteria described earlier, it will promote the app to the user for installation. The user will be offered the chance to install the app. This means you can distribute your PWA as a website, making it discoverable through web search, and also distribute it in app stores, so users can find it there.

This is a great example of the way PWAs can offer you the best of both worlds. It's also a good example of how progressive enhancement works with PWAs: if a user encounters your PWA on the web, using a browser that can't install it, they can use it just like a normal website.

The UI for installing a PWA from the web varies from one browser to another, and from one platform to another. For example, a browser might include an "Install" icon in the URL bar when the user navigates to the page:

![Chrome URL bar, showing PWA install icon](/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable/pwa-install.png)

When the user selects the icon, the browser displays a prompt asking if they want to install the PWA, and if they accept, the PWA is installed.

The prompt displays the name and icon for the PWA, taken from the `name` and `icons` manifest members.

### Browser support

Support for PWA installation promotion from the web varies by browser and by platform.

On desktop:

* Firefox and Safari do not support installing PWAs on any desktop operating systems. See Installing sites as apps, below.
* Chrome and Edge support installing PWAs on Linux, Windows, macOS, and Chromebooks.

On mobile:

* On Android, Firefox, Chrome, Edge, Opera, and Samsung Internet Browser all support installing PWAs.
* On iOS 16.3 and earlier, PWAs can only be installed with Safari.
* On iOS 16.4 and later, PWAs can be installed from the Share menu in Safari, Chrome, Edge, Firefox, and Orion.
### Installing sites as apps

Safari for deskop and mobile, and Edge for desktop also support installing any website as an app. However, this is not specific to PWA because the site doesn't need to meet the installability criteria described in this guide, and because the browser doesn't proactively promote the site for installation.

### Triggering the install prompt

A PWA can provide its own in-page UI for the user to open the install prompt, instead of relying on the UI provided by the browser by default. This enables a PWA to provide some context and a reason for the user to install the PWA, and can help make the install user flow easier to discover.

This technique relies on the `beforeinstallprompt` event, which is fired on the global `Window` object as soon as the browser has determined that the PWA is installable. This event has a `prompt()` method that shows the install prompt. So a PWA can:

* add its own "Install" button
* listen for the `beforeinstallprompt` event
* cancel the event's default behavior by calling `preventDefault()`
* in the event handler for its own "Install" button, call `prompt()`.

This is not supported on iOS.

### Customizing the installation prompt

By default, the install prompt contains the name and icon for the PWA. If you provide values for the `description` and `screenshots` manifest members, then, on Android only, these values will be shown in the install prompt, giving the user extra context and motivation to install the PWA.

The screenshot below shows what the install prompt for the PWAmp demo looks like on Google Chrome, running on Android:

![Install prompt for PWAmp on Android](/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable/pwamp-install-prompt-android.png)

Launching the app
-----------------

Once the PWA is installed, its icon is shown on the device alongside any other apps that the user has installed, and clicking the icon launches the app.

You can use the `display` manifest member to control the *display mode*: that is, how the PWA appears when it is launched. In particular:

* `"standalone"` indicates that the PWA should look and feel like a platform-specific application, with no browser UI elements
* `"browser"` indicates that the PWA should be opened as a new browser tab or window, just like a normal website.

If the browser does not support a given display mode, `display` will fall back to a supported display mode according to a predefined sequence. The `display_override` enables you to redefine the fallback sequence.


Caching - Progressive web apps

Caching
=======

When a user opens and interacts with a website, all the resources that the website needs, including the HTML, JavaScript, CSS, images, fonts, as well as any data explicitly requested by the app, are retrieved by making HTTP(S) requests. One of the most fundamental features of a PWA is the ability to explicitly cache some of the app's resources on the device, meaning that they can be retrieved without needing to send a request to the network.

There are two main benefits to caching resources locally: **offline operation** and **responsiveness**.

* **Offline operation**: Caching enables a PWA to function to a greater or lesser extent while the device does not have network connectivity.
* **Responsiveness**: Even if the device is online, a PWA will usually be much more responsive if its user interface is fetched from the cache, rather than the network.

The main drawback, of course, is **freshness**: caching is less appropriate for resources which need to be up to date. Also, for some types of requests, such as POST requests, caching is never appropriate.

This means that whether and when you should cache a resource is very dependent on the resource in question, and a PWA will typically adopt different strategies for different resources. In this guide we'll look at some common caching strategies for PWAs, and see which strategies make sense for which resources.

Caching technology overview
---------------------------

The main technologies on which a PWA can build a caching strategy are the Fetch API, the Service Worker API, and the Cache API.

### Fetch API

The Fetch API defines a global function `fetch()` for fetching a network resource, and `Request` and `Response` interfaces that represent network requests and responses. The `fetch()` function takes a `Request` or a URL as an argument, and returns a `Promise` that resolves to a `Response`.

The `fetch()` function is available to service workers as well as to the main app thread.

### Service Worker API

A service worker is a part of a PWA: it's a separate script that runs in its own thread, separate from the app's main thread.

Once the service worker is active, then whenever the app requests a network resource controlled by the service worker, the browser fires an event called `fetch` in the service worker's global scope. This event is fired not only for explicit `fetch()` calls from the main thread, but also implicit network requests to load pages and subresources (such as JavaScript, CSS, and images) made by the browser following page navigation.

By listening for the `fetch` event, the service worker can intercept the request and return a customized `Response`. In particular, it can return a locally cached response instead of always going to the network, or return a locally cached response if the device is offline.

### Cache API

The `Cache` interface provides persistent storage for `Request`/`Response` pairs. It provides methods to add and delete `Request`/`Response` pairs, and to look up a cached `Response` matching a given `Request`. The cache is available in both the main app thread and the service worker: so it is possible for one thread to add a response there, and the other to retrieve it.

Most commonly, the service worker will add resources to the cache in its `install` or `fetch` event handlers.

When to cache resources
-----------------------

A PWA can cache resources at any time, but in practice there are a few times when most PWAs will choose to cache them:

* **In the service worker's `install` event handler (precaching)**: When a service worker is installed, the browser fires an event called `install` in the service worker's global scope. At this point the service worker can *precache* resources, fetching them from the network and storing them in the cache.

**Note:** Service worker install time is not the same as PWA install time. A service worker's `install` event fires as soon as the service worker has been downloaded and executes, which will typically happen as soon as the user visits your site.

Even if the user never installs your site as a PWA, its service worker will be installed and activated.
* **In the service worker's `fetch` event handler**: When a service worker's `fetch` event fires, the service worker may forward the request to the network and cache the resulting response, either if the cache did not already contain a response, or to update the cached response with a more recent one.
* **In response to a user request**: A PWA might explicitly invite the user to download a resource to use later, when the device might be offline. For example, a music player might invite the user to download tracks to play later. In this case the main app thread could fetch the resource and add the response to the cache. Especially if the requested resource is big, the PWA might use the Background Fetch API, and in this case the response will be handled by the service worker, which will add it to the cache.
* **Periodically**: Using the Periodic Background Sync API, a service worker might fetch resources periodically and cache the responses, to ensure that the PWA can serve reasonably fresh responses even while the device is offline.
Caching strategies
------------------

A caching strategy is an algorithm for when to cache a resource, when to serve a cached resource, and when to get the resource from the network. In this section we'll summarize some common strategies.

This isn't an exhaustive list: it's just intended to illustrate the kinds of approaches a PWA can take.

A caching strategy balances offline operation, responsiveness, and freshness. Different resources have different requirements here: for example, the app's basic UI is likely to be relatively static, while it may be essential to have fresh data when displaying a product listing. This means that a PWA will typically adopt different strategies for different resources, and a single PWA might use all the strategies described here.

### Cache first

In this strategy we will precache some resources, and then implement a "cache first" strategy only for those resources. That is:

* For the precached resources, we will:
    + Look in the cache for the resource, and return the resource if it is found.
    + Otherwise, go to the network. If the network request succeeds, cache the resource for next time.
* For all other resources, we will always go to the network.

Precaching is an appropriate strategy for resources that the PWA is certain to need, that will not change for this version of the app, and that need to be fetched as quickly as possible. That includes, for example, the basic user interface of the app. If this is precached, then the app's UI can be rendered on launch without needing any network requests.

First, the service worker precaches static resources in its `install` event handler:

```js
const cacheName = "MyCache\_1";
const precachedResources = ["/", "/app.js", "/style.css"];

async function precache() {
  const cache = await caches.open(cacheName);
  return cache.addAll(precachedResources);
}

self.addEventListener("install", (event) => {
  event.waitUntil(precache());
});
```

In the `install` event handler, we pass the result of the caching operation into the event's `waitUntil()` method. This means that if caching fails for any reason, the installation of the service worker fails: conversely, if installation succeeded, the service worker can be sure that the resource was added to the cache.

The `fetch` event handler looks like this:

```js
async function cacheFirst(request) {
  const cachedResponse = await caches.match(request);
  if (cachedResponse) {
    return cachedResponse;
  }
  try {
    const networkResponse = await fetch(request);
    if (networkResponse.ok) {
      const cache = await caches.open("MyCache\_1");
      cache.put(request, networkResponse.clone());
    }
    return networkResponse;
  } catch (error) {
    return Response.error();
  }
}

self.addEventListener("fetch", (event) => {
  if (precachedResources.includes(url.pathname)) {
    event.respondWith(cacheFirst(event.request));
  }
});
```

We return the resource by calling the event's `respondWith()` method. If we don't call `respondWith()` for a given request, then the request is sent to the network as if the service worker had not intercepted it. So if a request is not precached, it just goes to the network.

When we add `networkResponse` to the cache, we must clone the response and add the copy to the cache, returning the original. This is because `Response` objects are streamable, so may only be read once.

You might wonder why we fall back to the network for precached resources. If they are precached, can't we be sure they will be in the cache? The reason is that it is possible for the cache to be cleared, either by the browser or by the user. Although this is unlikely, it would make the PWA unusable unless it can fall back to the network. See Deleting cached data.

### Cache first with cache refresh

The drawback of "cache first" is that once a response is in the cache, it is never refreshed until a new version of the service worker is installed.

The "cache first with cache refresh" strategy, also known as "stale while revalidate", is similar to "cache first", except that we always send the request to the network, even after a cache hit, and use the response to refresh the cache. This means we get the responsiveness of "cache first", but get a fairly fresh response (as long as the request is made reasonably often).

This is a good choice when responsiveness is important, and freshness is somewhat important but not essential.

In this version we implement "cache first with cache refresh" for all resources except JSON.

```js
function isCacheable(request) {
  const url = new URL(request.url);
  return !url.pathname.endsWith(".json");
}

async function cacheFirstWithRefresh(request) {
  const fetchResponsePromise = fetch(request).then(async (networkResponse) => {
    if (networkResponse.ok) {
      const cache = await caches.open("MyCache\_1");
      cache.put(request, networkResponse.clone());
    }
    return networkResponse;
  });

  return (await caches.match(request)) || (await fetchResponsePromise);
}

self.addEventListener("fetch", (event) => {
  if (isCacheable(event.request)) {
    event.respondWith(cacheFirstWithRefresh(event.request));
  }
});
```

Note that we update the cache asynchronously (in a `then()` handler), so the app does not have to wait for the network response to be received before it can use the cached response.

### Network first

The last strategy we'll look at, "network first", is the inverse of cache first: we try to retrieve the resource from the network. If the network request succeeds, we return the response and update the cache. If it fails, we try the cache.

This is useful for requests for which it is important to get the most fresh response possible, but for which a cached resource is better than nothing. A messaging app's list of recent messages might fall into this category.

In the example below we use "network first" for requests to fetch all resources located under the app's "inbox" path.

```js
async function networkFirst(request) {
  try {
    const networkResponse = await fetch(request);
    if (networkResponse.ok) {
      const cache = await caches.open("MyCache\_1");
      cache.put(request, networkResponse.clone());
    }
    return networkResponse;
  } catch (error) {
    const cachedResponse = await caches.match(request);
    return cachedResponse || Response.error();
  }
}

self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);
  if (url.pathname.match(/^\/inbox/)) {
    event.respondWith(networkFirst(event.request));
  }
});
```

There are still requests for which no response is better than a possibly-outdated response, and for which only a "network only" strategy is appropriate. For example, if an app is showing the list of available products, it will be frustrating to users if the list is out of date.

Deleting cached data
--------------------

Caches have a limited amount of storage space, and the browser may evict an app's cached data if the limit is exceeded. The specific limits and behavior are browser-specific: see Storage quotas and eviction criteria for details. In practice, eviction of cached data is a very rare event. The user can also clear an app's cache at any time.

A PWA should clean up any old versions of its cache in the service worker's `activate` event: when this event fires, the service worker can be sure that no previous versions of the service worker are running, so old cached data is no longer needed.

See also
--------

* Service Worker API
* Fetch API
* Storage quotas and eviction criteria
* Strategies for service worker caching on developer.chrome.com (2021)
* The Offline Cookbook on web.dev (2020)


What is a progressive web app? - Progressive web apps

What is a progressive web app?
==============================

A progressive web app (PWA) is an app that's built using web platform technologies, but that provides a user experience like that of a platform-specific app.

Platform-specific apps
----------------------

*Platform-specific apps* are developed for a specific operating system (OS) and/or class of device, like an iOS or Android device, generally using an SDK provided by the platform vendor. They are usually distributed using the vendor's app store, where the user can find and install them, and they subsequently seem to the user like a permanent extra feature of their device, expanding its capabilities in some way.

The benefits of platform-specific apps include:

* **Easy for users to access**: They get their own icon on the device, making it easy for users to find and open them.
* **Offline and background operation**: They are able to operate when the user is not interacting with them and when the device is offline. This, for example, enables a chat app to receive messages when it is not open, and display a notification to the user. It also enables a news app to update in the background so it can show fresh content even if the device is offline.
* **Dedicated UI**: They can implement their own distinctive, immersive UI.
* **OS integration**: They can be integrated into the host OS: for example, a messaging app can register as a share target, enabling users to select an image in the photo app and send it using the messaging app. They can also access device features such as the camera, GPS or accelerometer.
* **App store integration**: They are distributed using the app store, giving users a single place to find them and a consistent way to decide whether they want to install them.
Traditional websites
--------------------

Traditionally, websites are less like "something the user has" and more like "somewhere the user visits". Typically, a website: does not have a presence on the user's device when the user is not accessing it, can only be accessed by the user opening the browser and navigating to the site, and is highly dependent on network connectivity.

However, websites have some benefits over platform-specific apps, including:

* **Single codebase**: Because the web is inherently cross-platform, a website can run on different operating systems and device classes from a single codebase.
* **Distribution via the web**: The web is a great distribution platform. Websites are indexed by search engines, and can be shared and accessed just using URLs. A website can be distributed with no need to sign up to any vendor's app store.
Progressive web apps
--------------------

Progressive web apps combine the best features of traditional websites and platform-specific apps.

PWAs have the benefits of websites, including:

* PWAs are developed using standard web platform technologies, so they can run on multiple operating systems and device classes from a single codebase.
* PWAs can be accessed directly from the web.

PWAs also have many of the benefits of platform-specific apps, including:

* **PWAs can be installed on the device**. This means:
    + The PWA can be installed from platform's app store or installed directly from the web.
    + The PWA can be installed like a platform-specific app, and can customize the install process.
    + Once installed, the PWA gets an app icon on the device, alongside platform-specific apps.
    + Once installed, the PWA can be launched as a standalone app, rather than a website in a browser.
* **PWAs can operate in the background and offline**: a typical website is only active while the page is loaded in the browser. A PWA can:
    + Work while the device does not have network connectivity.
    + Update content in the background.
    + Respond to push messages from the server.
    + Display notifications using the OS notifications system.
* PWAs can use the whole screen, rather than running in the browser UI.
* PWAs can be integrated into the device, registering as share targets and sources, and accessing device features.
* PWAs can be distributed in app stores, as well as openly via the web.
### PWAs and the browser

When you visit a website in the browser, it's visually apparent that the website is "running in the browser". The browser UI provides a visible frame around the website, including UI features like back/forward buttons and a title for the page. The Web APIs your website calls are implemented by the browser engine.

PWAs typically look like platform-specific apps － they are usually displayed without the browser UI around them － but they are, as a matter of technology, still websites. This means they need a browser engine, like the ones in Chrome or Firefox, to manage and run them. With a platform-specific app, the platform OS manages the app, providing the environment in which it runs. With a PWA, a browser engine performs this background role, just like it does for normal websites.

![Diagram comparing the runtime environment for traditional websites, PWAs, and platform-specific apps](/en-US/docs/Web/Progressive_web_apps/Guides/What_is_a_progressive_web_app/pwa-environment.svg)

In our documentation for PWAs, we sometimes refer to the browser playing this background role. We might say, for example, "The browser starts a PWA's service worker when a push notification is received." Here, the browser's activity is entirely in the background. From the PWA's point of view, it might as well be the operating system that started it. For some systems, such as Chromebooks, there may not even be a distinction between "the browser" and "the operating system."

### Technical features of PWAs

Because PWAs are websites, they have the same basic features as any other website: at least one HTML page, which very probably loads some CSS and JavaScript. Like a normal website, the JavaScript loaded by the page has a global `Window` object and can access all the Web APIs that are available through that object.

Beyond that, a PWA has some additional features:

* A web app manifest file, which, at a minimum, provides information that the browser needs to install the PWA, such as the app name and icon.
* A service worker, which, at a minimum, provides a basic offline experience.

#### Web app manifest

A PWA must have a web app manifest, and the manifest must include enough information for the browser to install the PWA.

The manifest can define many other aspects of the PWA's appearance, such as theme color and background color, and its behavior, including its ability to act as a share target for data from other apps or to handle particular file types.

#### Service worker

A PWA must have a service worker, and the service worker must implement at least a minimal offline experience.

Service workers encourage an architecture in which the app's pages - that is, the traditional part of a website - implement the user interface, and the service worker implements a backend which can support offline and background operation, making the PWA behave more like an app than a website. This is because service workers can be started by the browser in the background when they are needed (for example, to handle a push notification).

### PWAs and single-page apps

Traditionally a website is built as a collection of interlinked pages. When the user clicks a link from one page in the site to another page in the same site, the browser loads the new page as a completely new entity, including the HTML and the subresources that the HTML loads, like CSS and JavaScript. In a single-page app, the site consists of a single HTML page, and when the user clicks internal links, this is handled by JavaScript fetching new content from the server and updating the relevant parts of the page.

Single-page apps can provide a user experience that is closer to platform-specific apps, so PWAs are often implemented as single-page apps. In particular, single-page apps make it easier to achieve a seamless user interface, in which the user is presented with a single, consistent page, and only the relevant parts of the page are updated as the user interacts with the app.

However, PWAs don't have to be single-page apps, and single-page apps don't have to be PWAs.

### Progressive enhancement

While progressive enhancement is a desirable attribute for most websites, it is especially important for PWAs, which expect to run on a wide range of devices and often use advanced Web APIs which may not be supported by all browsers.

One basic component of progressive enhancement is that, if the user visits your PWA on the web by entering its URL in a browser, the user can interact with the app like a normal website. But if the browser can install it, the user will be prompted to install it and the app will appear as a new feature on their device.

PWAs should perform feature detection for advanced APIs and provide acceptable fallback experiences.

For example, the Background Sync API enables a PWA to ask a service worker to make a network request as soon as the device has connectivity. A classic use case for this is messaging. Suppose the user composes a message, but when the user tries to send the message, the device is offline. The Background Sync API enables the device to send the message in the background once the device is connected. On a device that does not support Background Sync, the app should let the user know the message could not be sent, giving them the chance to try again later.


Expose common app actions as shortcuts - Progressive web apps

Expose common app actions as shortcuts
======================================

Many operating systems support showing shortcut menus, or jump-list, when the user right-clicks or long-presses an app icon. For example, on Windows, right-clicking on any pinned program in the taskbar shows a list of program-specific actions and recently opened files:

![The task bar in Windows, showing several pinned apps. The Firefox app icon was right-clicked, and the jump list is displayed, showing frequent tabs and common tasks](/en-US/docs/Web/Progressive_web_apps/How_to/Expose_common_actions_as_shortcuts/jump-list.png)

On Android, long-pressing an app icon also shows a list of common app actions:

![The Android app launcher, showing an app icon that's been long-pressed. The shortcut menu is displayed, showing common actions](/en-US/docs/Web/Progressive_web_apps/How_to/Expose_common_actions_as_shortcuts/android-shortcuts.png)

Progressive Web Apps (PWAs) can be installed on devices just like platform-native apps and, like their native counterparts, they can also define app shortcut menus to let users access common actions.

Shortcuts are only displayed by right-clicking or long-pressing the app icon, meaning they're only available once the PWA is installed on the user's device. To learn how to make your PWA installable, see making PWAs installable.

Why use shortcuts?
------------------

Defining shortcuts for your PWA can make users more productive by letting them access your app's main actions directly from their home screen. Additionally, defining shortcuts can help make your PWA feel more platform-native and therefore more familiar to your users.

Define shortcuts in the web app manifest
----------------------------------------

To define shortcuts for your PWA, use the `shortcuts` member of the web app manifest. This member is an array of objects defining each shortcut's name and URL, as well as the optional short name, description, and icons. For example, here's the web app manifest of a calendar app that defines two shortcuts:

```json
{
  "name": "Calendar",
  "start\_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "images/icon-256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ],
  "shortcuts": [
    {
      "name": "New event",
      "url": "/new-event"
    },
    {
      "name": "View today's events",
      "url": "/today"
    }
  ]
}
```

The most important properties of each shortcut object are:

`name`

The name of the shortcut, which is displayed in the shortcut menu. Make sure to keep it short but also descriptive enough to let users know what the shortcut does.

`url`

The URL to launch the PWA with when the user selects the shortcut. This URL can be absolute, in which case it should exist within the scope of the web app manifest. The URL can also be relative, in which case it's resolved relative to the PWA's start URL.

All other shortcut object properties are optional, but you should consider providing them to make the shortcut more useful to users:

`short_name`

A short name for the shortcut, which is displayed when there's not enough space to display the full name.

`description`

A description of the shortcut. This string can be accessed by assistive technologies, such as screen readers, to help users understand what the shortcut does.

`icons`

An array of image objects to display in the shortcut menu. Each image object is processed just like the `icons` member of the web app manifest, and can be used to provide different-sized icons for different device requirements.

See also
--------

* `shortcuts` manifest member
* Get things done quickly with app shortcuts on web.dev (2022)
* Define app shortcuts on learn.microsoft.com (2023)


Define your app icons - Progressive web apps

Define your app icons
=====================

Progressive Web Apps (PWAs) can be installed on devices just like other apps. Once a PWA is installed, its app icon appears on the device's home screen, dock, taskbar, or any other place where operating system native apps normally appear.

For example, on Windows, the taskbar can contain icons for both native and PWA apps side by side:

![The taskbar on Windows, showing the usual Windows icons, and then icons for Firefox and Word, which are native apps, but also icons for Spotify and PWAmp, which are PWAs](/en-US/docs/Web/Progressive_web_apps/How_to/Define_app_icons/windows-taskbar.png)

When creating a PWA, you can define your own set of icons to be used when the app is installed on a device. This article explains how to define your own app icons, which icon sizes to create, and how to make your icons support masking.

**Note:** The PWA app icon is not the same as the favicon image, which is displayed in places like the browser's address bar. PWAs can have both a favicon and an app icon. To learn more about favicons, see Adding custom icons to your site.

Design your icon
----------------

The first step in defining your app icon is to design it.

Most users recognize applications by their icons. Icons appear in many places through the operating system, including the home screen, taskbar, app launcher, or setting panels. Make sure your users can easily find your app by making its icon both visually appealing, and representative of your application.

The image should have a transparent background so it can be displayed on a variety of backgrounds. It should be at least 1024x1024 pixels, or scalable to that size, as this is the largest size your icon may be displayed at. You may also want to create icon versions with fewer details, to be used in places where the icon is displayed at smaller sizes.

Designing your icon as an SVG file is a good idea, as this will allow it to be scaled to any size without loss of quality.

Reference your icons in the web app manifest
--------------------------------------------

Use the `icons` web app manifest member to define a set of icons for your PWA.

As described in Create the necessary icon sizes, you should create multiple versions of your icon to ensure it appears correctly in all the places where it's used. This is why the `icons` member is an array of objects, each of which represents an icon, with its own size, type, and purpose. Each icon object has the following properties:

`src`

The URL of the icon image file.

`sizes`

The sizes the icon can be used for.

`type`

The MIME type of the image file which the operating system can use to quickly ignore images it does not support.

`purpose`

The OS-specific purpose of the image.

For example, the following web app manifest defines five PNG icons, each with a different size:

```json
{
  "name": "My PWA",
  "start\_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "icon-72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "icon-128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "icon-144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}
```

Create the necessary icon sizes
-------------------------------

Various operating systems use different icon sizes in different places and for different device capabilities. It's important to create multiple versions of your icon so it appears correctly in all the places where it's used.

For example, Windows can display your app icon as a 44x44 pixels image in the taskbar, or as a 150x150 pixels image in the start menu. Use the links below for more information about the icon sizes used by different operating systems and tips for creating effective icons:

* For Windows, see Define icons and a theme color on learn.microsoft.com.
* For Android, see Google Play icon design specifications on developer.android.com.
* For iOS and macOS, see App icons on developer.apple.com.

The sizes at which your icon is displayed depend on the operating system and may change over time. It's best to test your icon on all the devices and operating systems you want to support, and generate the sizes and image types that lead to the best results. You can also use a tool like PWA Image Generator to generate the necessary icon sizes for you from a single high-definition image.

Note that some operating systems support SVG icons which can help reduce the number of images you need to create since SVG can be scaled to any size automatically. If your SVG icon doesn't scale down to small sizes nicely. creating additional icons with fewer details and less complexity may be necessary. To support all OSs and small sizes, create PNG versions of your icon as well.

The following web app manifest example uses a WebP image for the small icon, an ICO image for medium size icons, and a scalable SVG image for higher resolution icons:

```json
{
  "name": "My PWA",
  "start\_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "icon-small.webp",
      "sizes": "48x48",
      "type": "image/webp"
    },
    {
      "src": "icon-medium.ico",
      "sizes": "72x72 96x96 128x128 256x256"
    },
    {
      "src": "icon-high.svg",
      "sizes": "257x257"
    }
  ]
}
```

Learn more about how to reference the various icon sizes in Reference your icons in the web app manifest.

Support masking
---------------

Depending on the operating system and the device capabilities, a mask may be applied to your icon to match a particular shape. Maskable icons are adaptive icons that can be displayed in a variety of shapes that operating systems provide. For example, on Android, app icons can have a circular mask.

Your PWA app icon should specifically support masking to look well integrated with operating systems that apply masks. Icons that do not support masking may appear cropped or smaller than expected. On Android, non-maskable icons are centered within the circular mask and given a white background, which may not look good with your icon.

The following image illustrates the difference between a maskable and a non-maskable icon on Android:

![A non-maskable icon on the left, as a small square within the app icon circle. A maskable icon on the right, filling in the entire app icon circle](/en-US/docs/Web/Progressive_web_apps/How_to/Define_app_icons/maskable-icon-comparison.png)

To start making your app icon maskable, use the `purpose` property in the icon objects of your web app manifest, and set its value to `maskable`. For example:

```json
{
  "icons": [
    {
      "src": "icon-192-maskable.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}
```

Using the `purpose` property is just the last step in making your icon maskable. You need to start by making sure your icon looks well integrated with the host operating system by ensuring the important parts of the icon appear well within the mask's *safe zone*.

The safe zone is the area that's guaranteed to always be visible when the mask is applied and is defined as a circle which diameter is 80% of the icon's minimum dimension.

![Illustration of the safe area within a maskable icon](/en-US/docs/Web/Progressive_web_apps/How_to/Define_app_icons/maskable-icon-safe-area.png)

For example, if your icon is a square, make sure that the square is entirely visible within the safe zone and that its corners are not cut off.

Finally, give your maskable icon an opaque background color to fill the entire icon area.

You can also use tools like Maskable.app to preview how your icon will look when masked on different operating systems.

See also
--------

* `icons` manifest member
* App design on web.dev
* Adaptive icon support in PWAs with maskable icons on web.dev
* Define icons and a theme color on microsoft.com
* PWA Image Generator on pwabuilder.com


Trigger installation from your PWA - Progressive web apps

Trigger installation from your PWA
==================================

**Warning:** The technique described here depends on the `beforeinstallprompt` event, which is non-standard and currently only implemented in Chromium-based browsers.

By default, if the user visits your website, and the browser determines that the site is installable as a PWA, then the browser will display some built-in UI — an icon in the URL bar, for example — to install the site. If the user clicks the icon, then the browser shows an install prompt containing, at a minimum, the app's name and icon. If the user agrees to install the app, then it will be installed.

However, you can implement your own in-app UI to ask the user if they want to install the app, which will trigger the install prompt. The benefits of this are:

* You can provide more context about the app, explaining to the user why they might want to install it as a PWA.
* An in-app install UI is likely to be easier for users to discover and understand than the browser's default UI.
Adding an in-app install UI
---------------------------

First, add some UI to the app indicating that the user can install it. For example:

```html
<button id="install" hidden>Install</button>
```

We're setting the button's `hidden` attribute, because if the user visits the app with a browser that can't install it, we don't want the install UI to be visible. Next, we'll see how to make the button visible only on browsers that support installing PWAs locally.

Listening for beforeinstallprompt
---------------------------------

As soon as the browser has determined that it can install the app, it fires the `beforeinstallprompt` event in the global `Window` scope.

In our main app code, we will listen for this event:

```js
// main.js

let installPrompt = null;
const installButton = document.querySelector("#install");

window.addEventListener("beforeinstallprompt", (event) => {
  event.preventDefault();
  installPrompt = event;
  installButton.removeAttribute("hidden");
});
```

The event handler here does three things:

* Call `preventDefault()` on the event. This prevents the browser from displaying its own install UI.
* Take a reference to the event object that's passed into the handler. This is an instance of `BeforeInstallPromptEvent`, and is what will enable us to prompt the user to install the app.
* Reveal our in-app install UI by removing the `hidden` attribute on the button.
Triggering the install prompt
-----------------------------

Next, we need to add a click handler to our in-app install button:

```js
// main.js

installButton.addEventListener("click", async () => {
  if (!installPrompt) {
    return;
  }
  const result = await installPrompt.prompt();
  console.log(`Install prompt was: ${result.outcome}`);
  disableInAppInstallPrompt();
});

function disableInAppInstallPrompt() {
  installPrompt = null;
  installButton.setAttribute("hidden", "");
}
```

The `installPrompt` variable was initialized with the `BeforeInstallPromptEvent` object in our `beforeinstallprompt` event handler. If `installPrompt` hasn't been initialized, for any reason, we do nothing.

Otherwise we call its `prompt()` method. This shows the install prompt, and returns a `Promise` which resolves with an object indicating whether the app was installed or not. In particular, its `outcome` property is `"accepted"` if the user chose to install the app, or `"dismissed"` if they dismissed the prompt.

Either way, we must reset our state after calling `prompt()`, because we can only call it once for each `BeforeInstallPromptEvent` instance. So we reset our `installPrompt` variable and hide the install button again.

Responding to app install
-------------------------

Depending on the browser and platform, the browser may still offer its own UI to install the app. This means that the app may still be installed without going through our in-app install UI. If this happens, we want to disable the in-app install UI, or we will show it in an app that's already been installed.

To do this we can listen to the `appinstalled` event, which fires in the global `Window` scope when the app has been installed:

```js
// main.js

window.addEventListener("appinstalled", () => {
  disableInAppInstallPrompt();
});

function disableInAppInstallPrompt() {
  installPrompt = null;
  installButton.setAttribute("hidden", "");
}
```

Responding to platform-specific apps being installed
----------------------------------------------------

One case not covered by the above examples is where you have a platform-specific version of the app as well as a web app, and you want to personalize the web app experience depending on whether the platform-specific app is already installed. You might not want to invite users to install the PWA if they already have the platform-specific app installed, and/or you might want to invite them to head over to the platform-specific app to view content.

This can be handled with the `Navigator.getInstalledRelatedApps()` method, which allows you to detect installed related platform-specific apps (or PWAs) and respond appropriately.

For example:

```js
const relatedApps = await navigator.getInstalledRelatedApps();

// Search for a specific installed platform-specific app
const psApp = relatedApps.find((app) => app.id === "com.example.myapp");

if (psApp) {
  // Update UI as appropriate
}
```

This method could also be combined with `beforeinstallprompt` to suppress the browser's install UI based on the availability of a platform-specific app:

```js
window.addEventListener("beforeinstallprompt", async (event) => {
  const relatedApps = await navigator.getInstalledRelatedApps();

  // Search for a specific installed platform-specific app
  const psApp = relatedApps.find((app) => app.id === "com.example.myapp");

  if (psApp) {
    event.preventDefault();
    // Update UI as appropriate
  }
});
```

See also
--------

* Making PWAs installable
* `beforeinstallprompt` event
* How to provide your own in-app install experience on web.dev (2021)


Share data between apps - Progressive web apps

Share data between apps
=======================

Application sharing is the ability of one application to pass information or data to another application on the same device. This feature is useful for users as it allows them to share information between two applications without the need for these applications to have prior knowledge of each other.

For example, on your mobile device, you can share photos or videos from your photo app with another application that accepts images, such as an email application. This sharing pattern is orchestrated by the operating system (OS) where the two applications are installed:

1. When the photo sharing is initiated by the user, the photo application prepares the data for the selected image, and hands it over to the operating system.
2. The operating system selects the list of applications that are able to handle the shared image data and displays them to the user.
3. Once the user selects one of the target applications, the operating system launches it with the shared image.

Progressive Web Apps (PWAs) also have the ability to share information using the same OS-orchestrated pattern. PWAs can both share data and accept shared data.

When building a PWA, accepting shared data can make your PWA feel more familiar and naturally integrated into your user's devices.

Sharing data with other apps
----------------------------

To make it possible for users to share data with other apps from your PWA, use the Web Share API. The Web Share API allows your app to share text, links, or files with other apps via the underlying operating system share mechanism.

To share data, use the `navigator.share()` method in response to a user action, such as a button click.

### Checking for support

Before displaying content-sharing UI in your application, check to ensure the Web Share API feature is supported. Even the browsers that support the Web Share API don't all support sharing all types of data. Therefore, it's a good practice to use the `navigator.canShare()` method first to validate whether the data you intend to share is, indeed, shareable from the browser that's running your app.

This example shows how to check if the Web Share API is supported and if the data can be shared:

```js
function canBrowserShareData(data) {
  if (!navigator.share || !navigator.canShare) {
    return false;
  }

  return navigator.canShare(data);
}

const sharedDataSample = {
  title: "Some text title",
  text: "More text",
  url: "A url we want to share",
};

if (canBrowserShareData(sharedDataSample)) {
  // Enable the share button in the UI.
  renderAppSharingUI();
} else {
  // We can't share on this browser/operating system.
}
```

### Handling exceptions

The `navigator.share()` method returns a `Promise` that may be rejected in cases such as when the shared data is incorrect, when the user aborts the share operation, or when the data transmission failed.

It's therefore important to catch the promise rejection in order to avoid errors in your app's JavaScript code.

```js
async function shareData(data) {
  try {
    await navigator.share(data);
    // The data was shared successfully.
  } catch (e) {
    // The data could not be shared.
    console.error(`Error: ${e}`);
  }
}
```

### Sharing text data

The following example demonstrates how to share a link and some text when a button in the app is clicked. The `canBrowserShareData` function used in the example is described in Checking for support and not repeated here.

```js
// Retrieve the button from the DOM. The button is hidden for now.
const button = document.querySelector("#share");

if (canBrowserShareData({ text: "text", url: "https://example.com" })) {
  // Show the button.
  button.style.display = "inline";

  // Listen for click events on the button to share data.
  button.addEventListener("click", async () => {
    try {
      await navigator.share({
        text: "An MDN article to learn how to share data between apps",
        url: "https://developer.mozilla.org/docs/Web/Progressive\_web\_apps/How\_to/Share\_data\_between\_apps",
      });

      console.log("The URL was successfully shared");
    } catch (err) {
      console.error(`The URL could not be shared: ${err}`);
    }
  });
}
```

### Sharing files

The following code example demonstrates how to share a file when a button in the app is clicked. The `canBrowserShareFiles` function is used to display the share button only if the browser supports sharing files.

```js
function canBrowserShareFiles() {
  if (!navigator.share || !navigator.canShare) {
    return false;
  }

  // Create some test data with a file, to check if the browser supports
  // sharing it.
  const testFile = new File(["foo"], "foo.txt", { type: "text/plain" });
  const data = { files: [testFile] };

  return navigator.canShare(data);
}

// Retrieve the button from the DOM. The button is hidden for now.
const button = document.querySelector("#share");

if (canBrowserShareFiles()) {
  // The browser supports sharing files. Show the button.
  button.style.display = "inline";

  // Listen for clicks on the button and share a file.
  button.addEventListener("click", async () => {
    try {
      // Get the file to be shared. This function should return a File
      // object, perhaps by creating it dynamically, or retrieving it
      // from IndexedDB.
      const file = await getTheFileToShare();

      await navigator.share({
        title: "My shared file",
        files: [file],
      });

      console.log("The file was successfully shared");
    } catch (err) {
      console.error(`The file could not be shared: ${err}`);
    }
  });
}
```

For more information, see the sharing files example on the `navigator.share()` method page.

Handling shared data from other apps
------------------------------------

To register your PWA as a target of other apps' shared data, use the Web Share Target API and, in particular, the `share_target` web app manifest member.

The `share_target` manifest member allows an installed PWA to be registered, at the operating system level, as a potential target for content shared by other apps. This means that when a user shares some data that's compatible with your PWA, from another app, the operating system will list your PWA alongside other typical share targets like email or messaging apps. Note that the PWA must be installed to be displayed as a potential target for receiving shared data.

The information you provide with the `share_target` member, in your manifest file, defines which data your app can be a target for, and how the operating system should launch your app when the user selects it as the target.

### Handling text data

Here is a web app manifest example using the `share_target` member:

```json
{
  "name": "ChattyBox",
  "start\_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "images/icon-256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ],
  "share\_target": {
    "action": "/share-handler",
    "method": "GET",
    "params": {
      "text": "description",
      "url": "link"
    }
  }
}
```

When your app is selected by the user to handle another apps' shared content, your app is launched and the shared content is passed to it in a similar way to that in which `<form>` elements are submitted.

In the previous web app manifest code example, when the ChattyBox app is selected as a target, it is launched by making an HTTP `GET` request at the `/share-handler` URL, with the shared data passed as request parameters named `description` and `link`.

The `GET` request will look like this: `/shared-handler?description=...&link=...`.

Your app's main JavaScript code can then retrieve the shared data by using the URLSearchParams interface:

```js
const url = new URL(document.location);
const sharedDescription = url.searchParams.get("description");
const sharedLink = url.searchParams.get("link");
```

For more information, see the example Receive share data using GET on the `share_target` web app manifest member page.

### Handling shared files

In the previous example, text data was handled as a `GET` request. However, handling files requires the use of a `POST` request with a `multipart/form-data` encoding type.

The following code snippet shows how a PWA can be configured to accept different types of shared files:

```json
{
  "name": "ChattyBox",
  "start\_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "images/icon-256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ],
  "share\_target": {
    "action": "/share-file-handler",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "files": [
        {
          "name": "textFiles",
          "accept": ["text/plain", ".txt"]
        },
        {
          "name": "htmlFiles",
          "accept": ["text/html", ".html"]
        },
        {
          "name": "images",
          "accept": ["image/jpeg", "image/png", "image/webp", "image/gif"]
        }
      ]
    }
  }
}
```

As this example shows, each file object in the `files` property must have a `name` property and an `accept` property. The `accept` property must specify the accepted MIME types or file extensions.

When the app is selected by the user to handle a shared file (or files), the app is launched with a `POST` request at the `/share-file-handler` URL, with encoded form data.

Because this is a `POST` request, your app's main JavaScript code can't access the form data directly. You can handle the submitted files in your server-side code, by receiving them at the `/share-file-handler` URL end point. However, for a better user experience that works offline, you can handle the files in your service worker code with a `fetch` event handler, as shown here:

```js
// service-worker.js

self.addEventListener("fetch", (event) => {
  // Only use this event listener for POST requests sent to /share-file-handler.
  const url = new URL(event.request.url);
  if (
    event.request.method !== "POST" ||
    url.pathname !== "/share-file-handler"
  ) {
    return;
  }

  event.respondWith(
    (async () => {
      // Get the data from the submitted form.
      const formData = await event.request.formData();

      // Get the submitted files.
      const textFiles = formData.getAll("textFiles");
      const htmlFiles = formData.getAll("htmlFiles");
      const imageFiles = formData.getAll("images");

      // Send the files to the frontend app.
      sendFilesToFrontend(textFiles, htmlFiles, imageFiles);

      // Redirect the user to a URL that shows the imported files.
      return Response.redirect("/display-new-files", 303);
    })(),
  );
});
```

In this code example, the shared files are extracted from the form data and the user is redirected to a different page. It's up to you, using the code in your service worker, to handle the extracted files as you want. For example, you can send them to your app's main JavaScript code using the `Worker.postMessage()` method or store them in an `IndexedDB` database which can be accessed both by your service worker and app's main JavaScript code.

For more information, see the receiving shared files example on the `share_target` web app manifest member page.

See also
--------

* Web Share API
* `share_target` manifest member
* Integrate with the OS sharing UI with the Web Share API on web.dev
* Receiving shared data with the Web Share Target API on developer.chrome.com
* Share content with other apps on microsoft.com