PWA (Progressive Web Apps) have been with us for some time now. Yet, each time I try explaining it to clients, the same question pops up: “Will my users be able to install the app using app stores?” The answer has traditionally been no, but this changed with Chrome 72 which shipped a new feature called TWA (Trusted Web Activities).
Trusted Web Activities are a new way to integrate your web-app content such as your PWA with yourAndroid app using a protocol based on Custom Tabs.
In this article, I will use Netguru’s existing PWA (Wordguru) and explain step-by-step what needs to be done to make the application available and ready to be installed straight from the Google Play app store.
Some of the things we cover here may sound silly to any Android Developers out there, but this article is written from the perspective of a front-end developer, particularly one who has never used Android Studio or created an Android Application. Also, please do note that a lot of what we’re covering here is still extremely experimental since it’s limited to Chrome 72.
Step 1: Set up a Trusted Web Activity
Setting up a TWA doesn’t require you to write any Java code, but you will need to have Android Studio. If you’ve developed iOS or Mac software before, this is a lot like Xcode in that it provides a nice development environment designed to streamline Android development. So, grab that and meet me back here.
Create a new TWA project in Android Studio
Did you get Android Studio? Well, I can’t actually hear or see you, so I’ll assume you did. Go ahead and crack it open, then click on “Start a new Android Studio project.” From there, let’s choose the “Add No Activity” option, which allows us to configure the project.
The configuration is fairly straightforward, but it’s always good to know what is what:
- Name The name of the application (but I bet you knew that).
- Package name: An identifier for Android applications on the Play Store. It must be unique, so I suggest using the URL of the PWA in reverse order (e.g.
com.netguru.wordguru
). - Save location: Where the project will exist locally.
- Language: This allows us to select a specific code language, but there’s no need for that since our app is already, you know, written. We can leave this at Java, which is the default selection.
- Minimum API level: This is the version of the Android API we’re working with and is required by the support library (which we’ll cover next). Let’s use API 19.
There are few checkboxes below these options. Those are irrelevant for us here, so leave them all unchecked, then move on to Finish.

Add TWA Support Library
A support library is required for TWAs. The good news is that we only need to modify two files to fill that requirement and the both live in the same project directory: Gradle Scripts
. Both are named build.gradle
, but we can distinguish which is which by looking at the description in the parenthesis.

There’s a Git package manager called JitPack that’s made specifically for Android apps. It’s pretty robust, but the bottom line is that it makes publishing our web app a breeze. It is a paid service, but I’d say it’s worth the cost if this is your first time getting something into the Google Play store.
Editor Note: This isn’t a sponsored plug for JitPack. It’s worth calling out because this post is assuming little-to-no familiarity with Android Apps or submitting apps to Google Play and it has less friction for managing an Android App repo that connects directly to the store. That said, it’s totally not a requirement.
Once you’re in JitPack, let’s connect our project to it. Open up that build.gradle (Project: Wordguru)
file we just looked at and tell it to look at JitPack for the app repository:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
...
}
}
OK, now let’s open up that other build.gradle
file. This is where we can add any required dependencies for the project and we do indeed have one:
// build.gradle (Module: app)
dependencies {
...
implementation 'com.github.GoogleChrome:custom-tabs-client:a0f7418972'
...
}
TWA library uses Java 8 features, so we’re going to need enable Java 8. To do that we need to add compileOptions
to the same file:
// build.gradle (Module: app)
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
...
}
There are also variables called manifestPlaceholders
that we’ll cover in the next section. For now, let’s add the following to define where the app is hosted, the default URL and the app name:
// build.gradle (Module: app)
android {
...
defaultConfig {
...
manifestPlaceholders = [
hostName: "wordguru.netguru.com",
defaultUrl: "https://wordguru.netguru.com",
launcherName: "Wordguru"
]
...
}
...
}
Provide app details in the Android App Manifest
Every Android app has an Android App Manifest (AndroidManifest.xml
) which provides essential details about the app, like the operating system it’s tied to, package information, device compatibility, and many other things that help Google Play display the app’s requirements.
The thing we’re really concerned with here is Activity (<activity>
). This is what implements the user interface and is required for the “Activities” in “Trusted Web Activities.”
Funny enough, we selected the “Add No Activity” option when setting up our project in Android Studio and that’s because our manifest is empty and contains only the application tag.
Let’s start by opening up the manfifest file. We’ll replace the existing package
name with our own application ID and the label
with the value from the manifestPlaceholders
variables we defined in the previous section.
Then, we’re going to actually add the TWA activity by adding an <activity>
tag inside the <application>
tag.
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.netguru.wordguru"> // highlight
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="${launcherName}" // highlight
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="android.support.customtabs.trusted.LauncherActivity"
android:label="${launcherName}"> // highlight
<meta-data
android:name="android.support.customtabs.trusted.DEFAULT_URL"
android:value="${defaultUrl}" /> // highlight
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="https"
android:host="${hostName}"/> // highlight
</intent-filter>
</activity>
</application>
</manifest>
And that, my friends, is Step 1. Let’s move on to Step 2.
Step 2: Verify the relationship between the website and the app
TWAs require a connection between the Android application and the PWA. To do that, we use Digital Asset Links.
The connection must be set on both ends, where TWA is the application and PWA is the website.
To establish that connection we need to modify our manifestPlaceholders
again. This time, we need to add an extra element called assetStatements
that keeps the information about our PWA.
// build.gradle (Module: app)
android {
...
defaultConfig {
...
manifestPlaceholders = [
...
assetStatements: '[{ "relation": ["delegate_permission/common.handle_all_urls"], ' +
'"target": {"namespace": "web", "site": "https://wordguru.netguru.com"}}]'
...
]
...
}
...
}
Now, we need to add a new meta-data
tag to our application
tag. This will inform the Android application that we want to establish the connection with the application specified in the manifestPlaceholders
.
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="${packageId}">
<application>
...
<meta-data
android:name="asset_statements"
android:value="${assetStatements}" />
...
</application>
</manifest>
That’s it! we just established the application to website relationship. Now let’s jump into the conversion of website to application.
To establish the connection in the opposite direction, we need to create a .json
file that will be available in the app’s /.well-known/assetlinks.json
path. The file can be created using a generator that’s built into Android Studio. See, I told you Android Studio helps streamline Android development!
We need three values to generate the file:
- Hosting site domain: This is our PWA URL (e.g.
https://wordguru.netguru.com/
). - App package name: This is our TWA package name (e.g.
com.netguru.wordguru
). - App package fingerprint (SHA256): This is a unique cryptographic hash that is generated based on Google Play Store keystore.
We already have first and second value. We can get the last one using Android Studio.
First we need to generate signed APK. In the Android Studio go to: Build → Generate Signed Bundle or APK → APK.

Next, use the existing keystore, if you already have one. If you need one, go to “Create new…” first.
Then let’s fill out the form. Be sure to remember the credentials as those are what the application will be signed with and they confirm your ownership of the application.

This will create a keystore file that is required to generate the app package fingerprint (SHA256). This file is extremely important as it is works as a proof that you are the owner of the application. If this file is lost, you will not be able to do any further updates to your application in the store.
Next up, let’s select type of bundle. In this case, we’re choosing “release” because it gives us a production bundle. We also need to check the signature versions.

This will generate our APK that will be used later to create a release in Google Play store. After creating our keystore, we can use it to generate required app package fingerprint (the SHA256).
Let’s head back to Android Studio, and go to Tools → App Links Assistant. This will open a sidebar that shows the steps that are required to create a relationship between the application and website. We want to go to Step 3, “Declare Website Association” and fill in required data: Site domain
and Application ID
. Then, select the keystore file generated in the previous step.

After filling the form press “Generate Digital Asset Links file” which will generate our assetlinks.json
file. If we open that up, it should look something like this:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.netguru.wordguru",
"sha256_cert_fingerprints": ["8A:F4:....:29:28"]
}
}]
This is the file we need to make available in our app’s /.well-known/assetlinks.json
path. I will not describe how to make it available on that path as it is too project-specific and outside the scope of this article.
We can test the relationship by clicking on the “Link and Verify” button. If all goes well, we get a confirmation with “Success!”

Yay! We’ve established a two-way relationship between our Android application and our PWA. It’s all downhill from here, so let’s drive it home.
Step 3: Get required assets
Google Play requires a few assets to make sure the app is presented nicely in the store. Specifically, here’s what we need:
- App Icons: We need a variety of sizes, including 48×48, 72×72, 96×96, 144×144, 192×192… or we can use an adaptive icon.
- High-res Icon: This is a 512×512 PNG image that is used throughout the store.
- Feature Graphic: This is a 1024×500 JPG or 24-bit PNG (no alpha) banner that Google Play uses on the app details view.
- Screenshots: Google Play will use these to show off different views of the app that users can check out prior to downloading it.

Having all those, we can proceed to the Google Play Store developers console and publish the application!
Step 4: Publish to Google Play!
Let’s go to the last step and finally push our app to the store.
Using the APK that we generated earlier (which is located in the AndroidStudioProjects
directory), we need to go to the Google Play console to publish our application. I will not describe the process of publishing an application in the store as the wizard makes it pretty straightforward and we are provided step-by-step guidance throughout the process.
It may take few hours for the application to be reviewed and approved, but when it is, it will finally appear in the store.
If you can’t find the APK, you can create a new one by going to Build → Generate signed bundle / APK → Build APK, passing our existing keystore file and filling the alias and password that we used when we generated the keystore. After the APK is generated, a notice should appear and you can get to the file by clicking on the “Locate” link.
Congrats, your app is in Google Play!
That’s it! We just pushed our PWA to the Google Play store. The process is not as intuitive as we would like it to be, but still, with a bit of effort it is definitely doable, and believe me, it gives that great filling at the end when you see your app displayed in the wild.
It is worth pointing out that this feature is still very much early phase and I would consider it experimental for some time. I would not recommend going with a production release of your application for now because this only works with Chrome 72 and above — any version before that will be able to install the app, but the app itself will crash instantly which is not the best user experience.
Also, the official release of custom-tabs-client
does not support TWA yet. If you were wondering why we used raw GitHub link instead of the official library release, well, that’s why.
“Not as intuitive”? It is absolutely ridiculous. The whole selling point of PWAs was “easy installation”, that we didn’t have to go through all of this bureaucratic coding crap (yes, crap, and i’m being as polite as i can tolerate) to get something out there.
None of this is necessary. All you should have had to do to get a PWA into some “PWA Store” is give them the URL. That’s it.
The rest of this is Google’s blatant laziness, telling us to keep using the crappy practices they forced onto us instead of them changing their own.
Yet they’re the ones with all the money, not us, right? Why should we expend more time to fit into their paradigm when they have all the money in the world to change theirs?
Time to say NO to publishing this way. PWAs are about “here’s the manifest, here’s the service worker, here are my icons, instant app”, and Google’s Play/Android team telling us anything to the contrary is them betraying the vision of PWAs they have been pushing for the last 3 years since they took “Chrome Applications” out of the Chrome Web Store.
Time to say no. Time for somebody to invent a better PWA store.
Awesome… how about for the windows store and the iOS store?
I just came here to say this. Thanks!
You, sir, deserve a cookie! You are absolutely right! It’s another idiotic idea from Google right after AMP.
Dude, I’m all with you!
Can’t believe I thought as much but I did!
Just gave this a shot, but I’m getting the below error. Any ideas why that might be?
ERROR: Failed to resolve: com.github.GoogleChrome:custom-tabs-client:a0f7418972
Note: I’m not using jitpack, I wanted to try to do it manually…
Never mind about the jitpack thing — I didn’t realize you could use the repository thing for free, and that it needs to go in the SECOND repositories section in the build.gradle. I’ve now got my app installed on my phone, pretty awesome, and super easy! Just wish there was a way to get rid of this address bar.
I’m not using jitpack, about the jitpack thing — I didn’t realize you could use the repository thing for free, and that it needs to go in the SECOND repositories section in the build.I wanted to try to do manually
Hi am new to Mobile apps, is this same as developing Android App using webview?
How would I go about adding a splash screen for this? And is there a way to disable the address bar?
All of that is covered in the manifest for creating the PWA in the first place, and out of scope for this particular article I should think.
The manifest describes the icon and thematic colors to use for the splash, as well as the layout rules (portrait, landscape, flexible) and how much of the target’s environment to see (run in browser with (read-only) address bar, total full-screen, or a mostly full-screen that leaves the top notification bar visible (my usual preference).
Basically, you should test your PWA by installing it on your phone first by hand (using Chrome, and after having used the app for a bit, the “add to home screen” option will be replaced with “Install “) and seeing how it behaves before going through this process of submitting to the play store. All the usual PWA rules apply (manifest, service worker, https with a properly signed cert).
Yes, I’ve got my PWAs set up as true PWAs, but the splash screen stuff and theme colors don’t seem to work when I set them up as Android apps using this tutorial. I imagine there’s probably some native feature of Android for splash screens, much like there is for their icons.
The process is not as intuitive as we would like it to be, I think it obvious and most of the computer related things are not intuitive. We need to focus and work hard and explore things to become productive.
uhh, thanks for this!
Thank you for this helpful article.
I have question
if my website using Adsense
Is my app will be compatible with Adsense policies ?
i found this in adsense policies page : https://support.google.com/admob/answer/48182?hl=en
“AdSense for content (AFC) and Ad Exchange (AdX) display ads are not supported through all WebView technologies. App developers wishing to monetize by publishing AFC and AdX display ads through a WebView must use one of the following supported viewing frames:
Android: Chrome Custom Tab
iOS: SFSafariViewController (iOS9 and iOS10 only)”
And in Support Forum i Found this Answer that TWA is Web view and adsense not allowed is this right ?
https://support.google.com/adsense/thread/20050641/is-adsense-allowed-with-twa-s?hl=en
Is TWA using Chrome Custom Tab or WebView ?
Thanks alot