# Distribute a PWA via app stores

> Wrap your PWA in a Trusted Web Activity for Google Play, package it with PWA Builder for the Microsoft Store, and hide install banners from users who have it.

import { Steps } from '@astrojs/starlight/components';

**Outcome:** your existing site, listed in an app store, with the store package generated
from it rather than rewritten. On Android, one route is a Trusted Web Activity — per
Chrome for Developers, a way to open your own web-app content, such as your PWA, from your
Android app using a protocol based on Custom Tabs, where the content is rendered by the
user's browser exactly as they would see it in the browser, except run fullscreen. On
Windows, per Microsoft, publishing a PWA to the Microsoft Store requires no code changes.

## Browser & ecosystem support

Per Chrome for Developers, Trusted Web Activity is available in Chrome on Android version
72 and above. Per Chrome for Developers, a Trusted Web Activity tries to adhere to the
user's default choice of browser: if that browser supports Trusted Web Activities it is
launched, and failing that, any installed browser that supports them is chosen. Per
Microsoft, the Microsoft Store route targets Windows and is driven through Microsoft
Partner Center rather than through the browser.

## Google Play, via a Trusted Web Activity

Per Chrome for Developers, content in a Trusted Web Activity is *trusted*: the app and the
site it opens are expected to come from the same developer, and this is verified using
Digital Asset Links. The capability is restricted to websites you own, and you prove
ownership by setting up those asset links.

Per Chrome for Developers, Bubblewrap is a set of libraries and a command line tool for
Node.js that helps developers generate, build and run PWAs inside Android applications
using Trusted Web Activity. Per its README, it requires Node.js 14.15.0 or above.

<Steps>

1. **Install the CLI and initialize from your manifest.** Per Chrome for Developers,
   Bubblewrap reads the web manifest, asks you to confirm the values used in the Android
   project, and generates the project from them.

   ```bash
   npm i -g @bubblewrap/cli
   bubblewrap init --manifest=https://my-twa.com/manifest.json
   bubblewrap build
   ```

2. **Install the build to test it.** Per Chrome for Developers, the build step outputs
   `app-release-signed.apk`, which can be installed on a development device for testing or
   uploaded to the Play Store for release. Per Chrome for Developers, `bubblewrap install`
   installs it on a connected device, and `adb install app-release-signed.apk` does the
   same.

3. **Publish `assetlinks.json`.** Per Chrome for Developers, Digital Asset Links consist
   of a file on your website pointing to your app plus metadata in your app pointing to
   your website; upload the file to `.well-known/assetlinks.json` relative to your site
   root so the browser can verify it.

4. **Create an app icon, then deploy.** Per Chrome for Developers, the next step after the
   quick start is to create an icon for your app — and once that is done, you can consider
   deploying your app to the Play Store.

</Steps>

Per Chrome for Developers, PWA Builder provides a GUI interface that uses the Bubblewrap
library to power the generation of Trusted Web Activity projects.

## Microsoft Store, via PWA Builder

Per Microsoft, publishing a PWA to the Microsoft Store requires no code changes: you
create an app reservation in Microsoft Partner Center, package your PWA using PWA Builder,
and then submit your package to the Store. Per Microsoft, the advantages it lists are
discoverability alongside other Windows apps, the trust Windows customers place in Store
downloads (which adhere to the Microsoft Store Policies), a consistent install experience,
and analytics about your app's health and usage in the Partner Center dashboard.

## Detect the store build from the web, and fall back

Once the same product exists both as a site and as a store listing, the web version should
stop advertising an install the user already completed. Per MDN,
`navigator.getInstalledRelatedApps()` returns a promise that resolves with an array of
objects representing any related platform-specific apps or PWAs the user has installed,
and MDN names exactly this use: removing "install our app" banners when the app is already
installed.

```js
async function shouldShowInstallBanner() {
  if (!('getInstalledRelatedApps' in navigator)) {
    // Not supported here — show the banner rather than hide a working call to
    // action behind a check the browser cannot answer.
    return true;
  }
  const related = await navigator.getInstalledRelatedApps();
  return related.length === 0;
}
```

Per MDN, this works only if two things are set up: the invoking web app must be listed in
the `related_applications` member of its manifest, and the platform-specific app or PWA
must define its relationship back — an Android app via the Digital Asset Links system, a
Windows UWP app via URI Handlers.

## Practical checklist

- **A signing-key mismatch downgrades your app to a Custom Tab.** Per Chrome for Developers,
  Digital Asset Links take into account the key the APK was signed with, and a common
  cause of verification failing is using the wrong signature — and failing verification
  means your site launches as a Custom Tab with browser UI at the top of the page instead
  of as a Trusted Web Activity.
- **Google Play may hold a different key than Bubblewrap did.** Per Chrome for Developers,
  Bubblewrap builds with a key set up during `init`, but when you publish in Google Play
  another key may be created for you depending on how you choose to handle signing. Check
  the asset links against the key Play actually ships.
- **Before asset links are configured, the app is not a TWA yet.** Per Chrome for
  Developers, on first run you will notice your website launches as a Custom Tab, not a
  Trusted Web Activity, because Digital Asset Links validation has not been set up.
- **The host app cannot read your web state.** Per Chrome for Developers, the host app has
  no direct access to web content in a Trusted Web Activity or to web state such as cookies
  and `localStorage`. Plan any native-to-web handoff as data passed in URLs.
- **The web experience is the product.** Per Chrome for Developers, the content is rendered
  by the user's browser in exactly the same way as in the browser, so anything broken on
  the site is broken in the store build.
- **`getInstalledRelatedApps()` is not dependable everywhere.** Per MDN it is experimental
  and not Baseline, because it does not work in some of the most widely used browsers, and
  it must be invoked in a top-level secure context — not inside an `<iframe>`.

## Where to go next

- [Trusted Web Activity reference](/reference/installation/twa/) — the mechanism in
  reference form.
- [`related_applications`](/reference/manifest/related-applications/) — the manifest member
  the detection above depends on.
- [`getInstalledRelatedApps()`](/reference/installation/get-installed-related-apps/) — the
  API's own entry, including its constraints.
- [Installability criteria](/reference/installation/installability-criteria/) — what the
  browser itself requires before offering an install.

← Back to the [Guides](/guides/) overview.