Updated: May 2026 · 9 min read

Published · Updated · 6 min read

Android publishing checklist before you upload a bundle

Upload the bundle you inspected, not a file that merely has a similar name. Each section below ends in a recorded value. A section you cannot record is a section you have not finished.

0. Name the artifact

Write the path of the Android App Bundle or APK, its length in bytes, and a whole-file SHA-256. If the file is a bundle, hash it locally. The APK Analyzer hashes APKs in the browser and does not unpack an .aab. If you need a browser listing, build a universal APK with bundletool and hash that file too, labeled as a universal APK so nobody uploads it by mistake.

Record applicationId from the Gradle variant you built, including any suffix. Record versionCode and versionName from that same variant. Confirm with aapt dump badging on an APK produced from the bundle. The analyzer’s “Possible versionCode” line is optional confirmation when it appears, and it is not the record. The three fields are defined in versionCode, versionName, and applicationId.

Open every track in Play Console and write down the highest versionCode already used for this application id. The candidate must be higher. A halted rollout still counts.

1. Manifest structure you can check in text

Export or locate the merged manifest. Paste it into the Manifest Validator. Keep the report. You want zero issues. Warnings about a missing launcher are acceptable only if this artifact is not a launcher app. Warnings about package shape should be explained, not ignored.

Then do the check the validator skips. Every activity, service, and receiver with an intent-filter needs android:exported when you target API 31 or higher. Search the merged XML. Install a build on an API 31+ system image if you can. The failure mode is described in exported components after Android 12.

Confirm android:debuggable is not true on the release manifest. The validator does not check that attribute. aapt dump badging prints a debuggable line when it is set.

2. Permissions and the forms that go with them

Paste the merged uses-permission lines into the Permissions Decoder. Copy the report into the release note. For each special badge, and for SMS, call log, all-files access, and background location, either remove the permission or fill the Play declaration that matches a permitted use. Unknown names are looked up in the platform docs before you call them fine. QUERY_ALL_PACKAGES will show up as unknown here.

Fill Data safety from the same list plus what the app actually transmits. The decoder does not know what you transmit. A camera permission with no camera feature is still a declaration you must be able to explain, and it is still a dialog the user will see if you request it. Walk the denial path on a clean profile, using the dialog sequence as the script.

Target API level has its own Play deadline, separate from this list. The version checker tells you which Android release an API number refers to. It does not tell you Play’s current target requirement. Read that requirement in Play Console while you are looking at the track.

3. Signing

Decide which keystore is the upload key enrolled for this app. apksigner verify --print-certs on the artifact you will upload must match the upload certificate in Play Console. The app signing certificate is allowed to be different. Testers who install from Play will run APKs signed by the app signing key, so their file hash will not match your upload hash. Record both fingerprints and the upload-file SHA-256. The distinction is in desk hash versus the installed APK.

If you are constructing the commands by hand, the APK signer guide prints keytool, zipalign, and apksigner in the order apksigner requires: align, then sign, then verify. The jarsigner option signs first and aligns second, and it is v1 only. Prefer the apksigner blocks. Do not paste keystore passwords into the release ticket. The page does not add password flags to the command.

Gradle-signed builds do not need those commands at all. You still need the verify step on the output file.

4. Native code, DEX, and the bundle

On a universal APK or on each ABI split, confirm the ABIs you intend are the ABIs present. 64-bit is required when you ship 32-bit native code. A base APK with no lib/ folder is not evidence that the app has no native code. Read what one ZIP listing can see.

DEX count is a sanity check, not a size budget. A sudden jump from one DEX file to four is a reason to look at dependencies before you upload, not a Play error by itself.

5. Store listing assets

The Play icon is 512 by 512. Adaptive icons in the app are 108dp layers masked to 72dp, with important art inside a 66dp circle. The icon generator can export 48, 72, 96, 144, 192, and 512 as flat PNGs from one source. It does not write mipmap-anydpi-v26 XML and it does not apply the launcher mask. Build the adaptive icon in Android Studio, then look at it on a circle and a squircle. The geometry and the generator’s limits are in the safe-zone article.

Screenshots and the feature graphic should show the build you are shipping. A screenshot of a debug menu or of a permission you just removed is a listing bug. Short description and full description should name the same features the permission declarations name. This site does not host your listing text.

6. Links you put in the listing, in email, or on a site

Collect every https URL and every custom scheme you expect to open the app. Run them through the deep-link tester so you have a parsed host and an am start -W line. Run the line on a device with the release-signed build, or with a build signed by the same certificate the assetlinks file names. With Play App Signing, assetlinks.json must name the app signing certificate. The tester does not fetch that file.

Check the fallback page with the app uninstalled. It should explain the content and link to the store. The intent URL generator can build an intent:// URL with S.browser_fallback_url for places that need that shape. Campaign mail should be tapped in a mail app, not only pasted into the tester. The matrix is in the deep-link test sheet.

7. The note you attach to the upload

applicationId:
versionCode:            (higher than every track)
versionName:
upload file SHA-256:
upload cert SHA-256:    (matches Play Console)
app signing cert:       (from Play Console, once)
aapt badging line:
validator:              (issues = 0, exported checked separately: yes/no)
permissions special: 
permissions unknown:
ABIs:
assetlinks host:        (verified on device: yes/no)
icon:                   (512 uploaded, adaptive checked on device: yes/no)

Fill the note above from the bundle you are about to ship, and keep the versionCode shown in Play Console with that note. This page has the blank template. It does not include a filled release note or a console screenshot.

If a line is blank, you are guessing. Guessing is how a staging application id and a reused versionCode get uploaded. The APK procedure is the way to fill the file-derived lines. This checklist is the list of lines. Keep the note with the tag that produced the bundle. A later crash report that names only versionName can be matched back to the integer and the hash if the note survived.