Skip to content

Distribute a PWA via app stores

Published

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.

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.

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.

  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.

    Terminal window
    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.

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

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

Section titled “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.

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.

  • 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>.

← Back to the Guides overview.