PWA manifest and assetlinks.json: get your web app ready for a Trusted Web Activity on Google Play
A Trusted Web Activity (TWA) is an Android app that opens your website full screen, which is how many web apps reach Google Play. Two files on your site make it work: a web app manifest that meets Chrome's install criteria, and /.well-known/assetlinks.json proving the app and the site belong together.
If assetlinks.json is missing, redirected, served with the wrong type or lists the wrong signing fingerprint, the app opens with a browser bar at the top instead of full screen.
The manifest
Chrome's installability criteria on web.dev: a short_name or name, a 192px and a 512px icon, a start_url, a display of fullscreen, standalone, minimal-ui or window-controls-overlay, prefer_related_applications absent or false, and the site "served over HTTPS". Android's TWA overview says TWAs are expected to "meet the same Add to Home Screen requirements".
{
"name": "Acme Rota",
"short_name": "Rota",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0f172a",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
Link it from index.html with <link rel="manifest" href="/guides/manifest.webmanifest">, and put the file and icons in public/.
assetlinks.json
Android's Digital Asset Links file, at exactly https://<your-domain>/.well-known/assetlinks.json:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.acmerota.app",
"sha256_cert_fingerprints": ["<SHA-256 of your app signing key>"]
}
}]
Android's hosting rules:
- "The
assetlinks.jsonfile is served with content-typeapplication/json." - It "must be accessible over an HTTPS connection".
- It "must be accessible without any redirects (no 301 or 302 redirects)", so serve it on the exact host the app opens, with no
wwwhop. - With several hosts, "you must publish the
assetlinks.jsonfile on each domain."
In a Vite, Lovable or Bolt project, put it at public/.well-known/assetlinks.json. Then open the address in a browser and check it shows JSON, not your app's homepage.
The fingerprint that trips everyone up
With Play App Signing, Google signs the app you upload. Android's docs: "the certificate fingerprint produced by running keytool locally will usually not match the one on users' devices." Copy the SHA-256 from Play Console (Release > Setup > App signing, which also offers a ready-made Digital Asset Links snippet). Chrome's TWA guide recommends listing both the app signing key and your upload key, so installs from Play and local test installs both pass.
Chrome's guide also gives the symptom: if your app is "launching as a Chrome Custom Tab (with the browser bar visible)", the file likely lacks the right fingerprint; adb logcat shows messages such as "Statement failure matching fingerprint".
Building the Android app
Bubblewrap, listed by Android as "a NodeJs library / CLI to generate and build Trusted Web Activity projects", reads your manifest to create the project.
What Pewterkit Check looks for
A free check reads your linked web app manifest and lists any missing installability fields (name, 192 and 512 icons with purpose "any", start_url, display mode). It requests /.well-known/assetlinks.json once and flags invalid JSON, a redirect, or a content type other than application/json. The signing fingerprint lives in Play Console, so compare that part yourself.
FAQ
Why does my TWA show a URL bar?
Usually Digital Asset Links failed: the file is missing, redirected, served with the wrong type, or lists a fingerprint that doesn't match the installed app's signing key.
Which SHA-256 fingerprint goes in assetlinks.json?
For installs from Google Play, the app signing key certificate from Play Console. Chrome's guide suggests adding your upload key's fingerprint too for local testing.
Do maskable icons count for the 192 and 512 sizes?
Keep regular icons (purpose "any", the default) at 192 and 512 as well, and add maskable versions as an extra. Pewterkit Check counts only icons with purpose "any" toward the two sizes.
Where does assetlinks.json go in a Vite app?
public/.well-known/assetlinks.json, which Vite copies to /.well-known/assetlinks.json in the build.
Sources
- web.dev: What does it take to be installable? — read 27 Oct 2026: manifest fields, display values, HTTPS
- Android Developers: Trusted Web Activities overview — read 27 Oct 2026: what a TWA is, Digital Asset Links trust, Add to Home Screen requirements, Bubblewrap
- Android Developers: Configure website associations (assetlinks.json) — read 27 Oct 2026: content type, HTTPS, no redirects, each domain, Play App Signing fingerprint
- Chrome for Developers: Android concepts for web developers (TWA) — read 27 Oct 2026: browser bar symptom, logcat message, both fingerprints
- Google Digital Asset Links: Getting started — read 27 Oct 2026: exact file location, statement example
- Vite: Static Asset Handling, the public directory — read 27 Oct 2026: public files copied to the build root
How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked Oct 27, 2026. Spotted something out of date? Tell us.
Related guides
- Stripe secret key exposed in your frontend code: rotate it, then move it to the server
- OpenAI API key in your React or Vite frontend: rotate it and move calls to a backend
- Supabase service_role key in your frontend bundle: replace it and switch the old key off
- Source maps in production: what they show and how to stop publishing them