Distribute a PWA via app stores
Published
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
Section titled “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:
-
Install the CLI (without
sudo) and initialise from the manifest URL.Terminal window npm i -g @bubblewrap/clibubblewrap init --manifest=https://notes.example/manifest.webmanifestinitcreates a signing key during the prompts; keep the keystore and its passwords, as this becomes the upload key. -
Build and install the result on a connected device.
Terminal window bubblewrap buildbubblewrap install # or: adb install app-release-signed.apkThe 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. -
Confirm which browser is providing the activity.
Terminal window adb logcat -v brief | grep -e TWAProviderPicker
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
Section titled “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:
[{ "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
Section titled “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
Section titled “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:
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.
{ "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.
See also
Section titled “See also”- Trusted Web Activity
related_applicationsgetInstalledRelatedApps()- Store policy
- Trusted Web Activities quick start guide (developer.chrome.com)
- Publish a PWA to the Microsoft Store (learn.microsoft.com)
← Back to the Guides overview.