# 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';

At the end of this guide the same site is listed on Google Play as a Trusted Web Activity
(TWA) and on the Microsoft Store as a packaged PWA, both generated from the live manifest
rather than rewritten, and the web version stops advertising an install to users who already
have the store build. A TWA renders your origin full screen in the user's browser, so the
web experience is the product; the store package is a shell plus a verified link to the site.

You need an installable PWA on a domain you control, Node.js 14.15.0 or later for the
Bubblewrap CLI (JDK 17 and the Android command-line tools are downloaded on first run if
absent), a Google Play developer account, and a personal Microsoft account enrolled in the
Windows Developer Program.

## Generate the Android project with Bubblewrap

Trusted Web Activity is available in Chrome on Android 72 and later; where Chrome lacks TWA
support it falls back to a Custom Tab with a toolbar. Bubblewrap reads the manifest, asks you
to confirm each value it copies into the Android project, and builds a signed APK:

<Steps>

1. Install the CLI (without `sudo`) and initialise from the manifest URL.

   ```bash
   npm i -g @bubblewrap/cli
   bubblewrap init --manifest=https://notes.example/manifest.webmanifest
   ```

   `init` creates a signing key during the prompts; keep the keystore and its passwords, as
   this becomes the upload key.

2. Build and install the result on a connected device.

   ```bash
   bubblewrap build
   bubblewrap install   # or: adb install app-release-signed.apk
   ```

   The build writes `app-release-signed.apk`. On first launch the site opens as a Custom Tab
   with the URL bar visible; that is expected until Digital Asset Links verification passes.

3. Confirm which browser is providing the activity.

   ```bash
   adb logcat -v brief | grep -e TWAProviderPicker
   ```

</Steps>

Run `bubblewrap doctor` if the build fails: it checks that the JDK and Android SDK recorded in
`~/.bubblewrap/config.json` exist and are the expected versions.

## Publish the Digital Asset Links statement

The URL bar disappears only when the site vouches for the app. Serve this file at
`https://notes.example/.well-known/assetlinks.json`; Android's App Links documentation
requires `Content-Type: application/json`, HTTPS, and no 301 or 302 redirects:

```json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "example.notes.twa",
    "sha256_cert_fingerprints": [
      "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
    ]
  }
}]
```

The fingerprint must be the certificate of the key that signs the APK users receive. Google
Play requires Play App Signing for AAB uploads, which means Google re-signs the app with an
"app signing key" that differs from the Bubblewrap upload key; Android's documentation notes
that a `keytool` fingerprint from your local keystore "will usually not match the one on
users' devices" in that case. Copy the SHA-256 of the "App signing key certificate" from Play
Console under **Release** > **Setup** > **App integrity**, and list both the upload and the
app signing fingerprints so test installs and Play installs both verify. A mismatched or
missing fingerprint silently downgrades the TWA to a Custom Tab with browser UI on top.

## Package for the Microsoft Store with PWA Builder

Microsoft documents that no code changes are needed: reserve the name in Partner Center,
package with PWA Builder, submit. In Partner Center choose **Apps and games** > **New
product** > **MSIX or PWA app**, enter a name and select **Reserve product name**, then open
**Product management** > **Product Identity** and copy **Package ID**, **Publisher ID**, and
**Publisher display name**. At pwabuilder.com enter the site URL under **Ship your PWA to app
stores**, click **Start**, fix anything listed under **Action Items**, then **Package For
Stores** > **Windows** > **Generate Package**, paste the three values, and **Download Package**.
The zip contains an `.msixbundle` and a `.classic.appxbundle`; upload both on the **Packages**
step of **Start your submission**. Microsoft states review typically completes within 24 to 48
hours. Front-end and service-worker changes reach store users without a resubmission; a
manifest change (icon, name, `file_handlers`, `share_target`) requires a new package because
the manifest is copied into it.

## Hide the install banner once the store app is present

`navigator.getInstalledRelatedApps()` resolves with the related apps the user has installed
and is the API MDN names for removing "install our app" banners. It requires a top-level
secure context (an `<iframe>` throws `InvalidStateError`) and is not Baseline, so default to
showing the banner when the method is missing:

```js
async function shouldShowInstallBanner() {
  if (!('getInstalledRelatedApps' in navigator)) return true;
  const related = await navigator.getInstalledRelatedApps();
  return related.length === 0;
}
```

The call only finds apps declared in the manifest's `related_applications` and whose own
side points back: the Android app through the Digital Asset Links file above, a Windows app
through URI handlers, and a PWA through a self-referencing `related_applications` entry.

```json
{
  "related_applications": [
    { "platform": "play", "id": "example.notes.twa", "url": "https://play.google.com/store/apps/details?id=example.notes.twa" },
    { "platform": "windows", "id": "12345Publisher.FieldNotes_abcdef123456" }
  ]
}
```

Leave `prefer_related_applications` absent or `false`; setting it to `true` on Android sends
users to Google Play instead of installing the web app.

:::observed
Microsoft's Store publishing page (learn.microsoft.com, updated 2026-09-02) documents that
Edge adds `Referer: app-info://platform/microsoft-store` to the first navigation of a PWA
installed from the Microsoft Store, readable client-side as `document.referrer`. Logging that
value on the start URL is how you count Store installs separately from browser installs
without a second build.
:::

## See also

- [Trusted Web Activity](/reference/installation/twa/)
- [`related_applications`](/reference/manifest/related-applications/)
- [`getInstalledRelatedApps()`](/reference/installation/get-installed-related-apps/)
- [Store policy](/ecosystem/stores/)
- [Trusted Web Activities quick start guide](https://developer.chrome.com/docs/android/trusted-web-activity/quick-start) (developer.chrome.com)
- [Publish a PWA to the Microsoft Store](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps/how-to/microsoft-store) (learn.microsoft.com)

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