How to install an .aab file on an Android phone (and share it with testers)
· 9 min read
Flutter, React Native and Android Studio all produce an .aab file by default for release builds, because that is what Google Play wants. Sooner or later someone sends that file to a tester, the tester taps it, and nothing happens. This guide explains why, then walks through the three practical ways to get an app bundle onto a phone: convert it yourself with bundletool, go through Google Play, or upload it to an install link service that converts it for you.
What an .aab file is, and why a phone cannot install it
An Android App Bundle is a publishing format. It holds the compiled code, resources and native libraries for every device the app supports, organised so that a store can cut it into pieces. The Android package installer does not read that format. It installs APKs: either one APK, or a base APK plus a set of split APKs installed together.
When you publish a bundle, Google Play generates split APKs for each device configuration (CPU architecture, screen density, language) and sends each phone only the pieces it needs. So the bundle is the input to an install, never the thing that gets installed. Renaming app-release.aab to .apk does not help either; the installer rejects it as a file it cannot parse.
To install a bundle you need something to do Play's job: build APKs from it and sign them. There are three common ways.
Option 1: build an APK yourself with bundletool
bundletool is Google's open source command line tool for app bundles, and it is the same tool Play uses on its side. It ships as a single jar on the project's GitHub releases page (version 1.18.3 at the time of writing) and needs Java. For installing on a connected phone you also need adb from the Android SDK platform tools, with USB debugging turned on.
Universal mode: one APK for any phone
Universal mode builds a single APK that contains everything in the bundle, for every device configuration. That is the version you want when you are sending the app to someone else, because the same file installs on any phone the app supports:
java -jar bundletool-all-1.18.3.jar build-apks \
--bundle=app-release.aab \
--output=app-release.apks \
--mode=universal \
--ks=upload-keystore.jks \
--ks-pass=file:ks.pwd \
--ks-key-alias=upload \
--key-pass=file:key.pwd
A few details about those flags:
--ksis the keystore,--ks-key-aliasthe key inside it. If you leave out the signing flags, bundletool signs with your local debug key (~/.android/debug.keystore), which is fine for your own phone and a poor choice for anything you hand out.--ks-passand--key-passacceptpass:yourpasswordorfile:/path/to/file. Thefile:form keeps the password out of your shell history and CI logs.- The output is an
.apksfile, which is a zip archive (an APK set), not an APK.
To get the installable file out of the set:
unzip -o app-release.apks universal.apk
adb install universal.apk
Or let bundletool install the set on the connected phone directly:
java -jar bundletool-all-1.18.3.jar install-apks --apks=app-release.apks
The universal.apk is the file you can put on a link, email or chat for testers. They open it on the phone and allow installs from that app once.
Connected device mode: only what one phone needs
If you are testing on your own phone and want to see roughly what Play would deliver, skip universal mode and target the connected device instead:
java -jar bundletool-all-1.18.3.jar build-apks \
--bundle=app-release.aab \
--output=device.apks \
--connected-device \
--ks=upload-keystore.jks \
--ks-pass=file:ks.pwd \
--ks-key-alias=upload
java -jar bundletool-all-1.18.3.jar install-apks --apks=device.apks
This produces a base APK plus the splits that match that phone, and install-apks installs them together. The result is smaller and closer to a Play install, but the splits only fit that kind of device, so the output is not something to share. You can also use --device-spec=device.json to build for a described device without one plugged in, and get-size total --apks=device.apks to estimate download size.
Universal APK vs what Play delivers
A universal APK is not the same file a user gets from Google Play. Play installs split APKs chosen per device; the universal APK carries every ABI, every screen density and every language at once. Expect it to be larger, and much larger when the app ships native libraries for several architectures. It behaves the same for functional testing, but do not use its size to judge your store download size, and do not use it to test anything that depends on how splits or on demand modules are delivered. For those, use Play's own testing tracks.
Option 2: Google Play internal testing or internal app sharing
If the app already exists in Play Console, Play can do the conversion and the testers install the real Play delivery.
- Internal testing track: up to 100 testers, added by email. Each tester needs a Google account and opts in through a link, then installs from the Play Store. Builds appear quickly and use the normal version code rules, so every upload needs a higher version code.
- Internal app sharing: upload a bundle or APK and get a link, with no review and no version code rules. Links expire after 60 days. The friction is on the tester side: they have to enable internal app sharing in the Play Store app by opening Settings, tapping the Play Store version several times to unlock developer options, and switching it on.
Both routes give testers exactly what Play would serve, signed with Google's app signing key, so the test build and the store build update each other. The cost is a Play Console app, Google accounts for testers, and some setup that is hard to explain to a client. A longer comparison of these routes is in unlisted app vs Ad Hoc vs install link.
Option 3: upload the .aab to AppForShare
If you do not want to run bundletool or ask testers to change Play settings, upload the bundle as it is. With a free AppForShare account, an .aab upload runs through bundletool in universal mode on our side and becomes one universal APK. Testers get an install link and a QR code, open it on the phone and install that APK. The .aab itself is not kept after the APK is made. Uploads without an account accept APK and IPA files only, so you need to sign up first.
The APK has to be signed, and there are two choices:
- Test key (Free and up). The first
.aabyou upload creates an AppForShare test key for your workspace. Every later bundle in that workspace is signed with the same key, so new test builds install over older ones. - Your own keystore (Pro and Team). An Admin adds the keystore in Settings (
.jks,.keystoreor.p12, with alias and passwords). It is checked with keytool, sealed with Google Cloud KMS, never shown again and can be removed at any time. If the plan lapses, new uploads go back to the test key.
The build page and the install page both show which key signed the APK, and the install page tells testers when they need to uninstall the store version first. Details are on the .aab upload feature page and limits per plan are on pricing.
Signing and updates: read this before you share
Android only lets an app update another app with the same package name if both are signed with the same key. This is the source of most "it will not install" reports with converted bundles, whichever of the three options you use.
- A test key or debug key APK cannot update the Play install. If a tester already has your app from Google Play, they must uninstall it before installing the test build, and uninstall the test build again to go back to Play. Uninstalling removes the app's local data.
- Your own key helps when it is the key Play installs carry. An APK signed with that key installs over the Play version and keeps the data. Be careful with Play App Signing, though: there Google holds the app signing key and signs what users download, while you hold only the upload key. An APK signed with the upload key is still a different signer from the Play install, so the uninstall rule applies. The details are in Android app signing: APK vs AAB.
- Keep one key per test channel. Builds signed by different laptops' debug keys cannot update each other. Pick one keystore for test builds, keep it in CI, and use it every time.
Many teams avoid the clash entirely by giving test builds their own applicationId, for example with a .beta suffix, so the test app and the store app sit side by side.
Common errors and what they mean
INSTALL_FAILED_UPDATE_INCOMPATIBLE
Shown by adb install when an app with the same package name is already installed with a different signature. On the phone itself the same problem appears as "App not installed" or "App not installed as package conflicts with an existing package". Uninstall the existing app, or sign with the same key as the installed copy.
INSTALL_PARSE_FAILED_NOT_APK or INSTALL_PARSE_FAILED_NO_CERTIFICATES
The first usually means you tried to install the .aab or the .apks archive itself instead of the universal.apk inside it. The second means the APK is unsigned or the signature is broken, often because the file was modified after signing or only partly downloaded. Rebuild with bundletool and transfer the file again.
"App not installed" with no other detail
On the phone this generic message covers several causes: a signature conflict (above), a test build with a lower version code than the installed app, not enough storage, or a corrupted download. Running adb install with the phone connected prints the specific error code.
Keystore was tampered with, or password was incorrect
This comes from Java when bundletool opens the keystore with the wrong --ks-pass. Check the password file for a trailing newline or space, and confirm the file is the keystore you think it is with keytool -list -keystore upload-keystore.jks. A wrong --ks-key-alias or --key-pass fails in a similar way; keytool -list shows the aliases the keystore contains.
INSTALL_FAILED_NO_MATCHING_ABIS
The app contains native libraries, but none for this phone's CPU architecture. A universal APK can only include the ABIs the bundle was built with, so if your Gradle config uses abiFilters to keep only arm64-v8a, it will not install on an x86_64 emulator or an old 32 bit phone. Add the missing ABI to the build, or test on matching hardware.
Which option to choose
- Testing on your own phone, today: bundletool with
--connected-device. - Testing exactly what Play will deliver, with testers who have Google accounts: Play internal testing or internal app sharing.
- Sending a build to QA, a client or anyone who should just tap a link: a universal APK, built yourself or by uploading the
.aabto AppForShare.
If your project is in Flutter, the end to end flow from flutter build appbundle to a tester's phone is in how to share a Flutter build with testers. To try the upload route, create a free account and drop in your next .aab.


