Google Play
Uploading Android builds to Play: tracks, the first manual upload, the Play Console declarations, and CI with fastlane. Read it before your first Android release or when you set up Android CI.
How it works
| Track | Review | Who gets it |
|---|---|---|
| Internal | None | Up to 100 named testers, minutes after upload |
| Closed | Yes | Tester lists or Google Groups |
| Open | Yes | Anyone who opts in |
| Production | Yes | Everyone, optionally a percentage at first |
How to build it
The first upload is by hand
Play refuses API uploads for an app until a person uploads the first AAB in Play
Console. Do that once per app, then let CI take over. On an app that has never been
published, CI must upload with release status draft; Play rejects completed releases
on draft apps.
The CI service account
- A Google Cloud service account with the Release manager role on the app in Play Console.
- Its JSON key is a CI secret. Never commit it.
- If Play denies access to every track, fail the job. Otherwise the version-code lookup returns nothing and the build goes out as version code 1.
fastlane
lane :internal do
upload_to_play_store(
package_name: ENV.fetch("PACKAGE_NAME"), # no fallback: an empty value must fail
track: "internal",
aab: "../build/app/outputs/bundle/prodRelease/app-prod-release.aab",
release_status: "completed",
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,
)
end
Play Console declarations
Every one of these is checked at review. Fill them before the first closed-track release, and update them before any build that changes what they describe.
| Declaration | What to watch |
|---|---|
| Data safety | Every data type the app and its SDKs collect, including device and advertising ids. Must match the privacy policy |
| Account deletion | An in-app path and a web URL where people can ask for deletion without the app |
| App access | A working demo account and steps. Missing it is a near-automatic rejection |
| Target audience | Matches the terms and the Apple age rating. Under 18 brings in the Families policy checks |
| Content rating (IARC questionnaire) | Answer honestly; it is compared with what reviewers see |
| Child safety standards | Social and community apps must publish their standards and offer in-app reporting |
| Advertising ID | If any SDK uses it, declare it, and keep the AD_ID permission. If not, remove the permission in the manifest |
| Store listing | No "#1", "best", rankings or claims in text or in screenshot images |
Target SDK and libraries
Google raises the required targetSdk and Billing Library version about once a year. A
build below them cannot be uploaded at all. Put the deadline in the team calendar.
Rules
Update the Data safety form before you upload a build that collects something new. A build that collects data its declaration denies is a policy violation from the moment it is live.
Test release builds by installing them from a Play track. Sideloaded release builds fail Play Integrity and in-app purchases.
CI build scripts never fall back to a default package name. A fallback to an old or wrong package uploads to the wrong app, or fails in a confusing way.
Mistakes we made
- A listing was rejected for screenshot text. Two images said things like "the #1 app in the world". Claims in images count as listing text.
- A build shipped with the advertising-id permission restored while Data safety still said "no advertising id". The permission came back for an attribution SDK. Update the declaration first, upload second.
- CI ran out of memory. An 8 GB Gradle heap plus the Kotlin daemon was more than the runner had. Size the heap to the runner.
- CI decoded secrets that no longer existed after a keystore rename, and later pinned a framework version older than the project's.
- A Gemfile.lock made with the laptop's system Ruby crashed on CI. Use the same Ruby version locally and in CI.
- Version codes 1 and 2 were already used by manual uploads, so CI's first codes collided. CI now adds an offset.
- The production upload is still manual from a laptop. It works, but it depends on one person's machine and keys. Move it to CI with a manual approval step.
Checklist
- First AAB uploaded by hand; CI uses
draftuntil the app is published. - CI service account has Release manager; job fails if Play denies access.
- Data safety, deletion URL, app access, target audience, content rating done.
- Advertising-id permission matches the declaration.
- Listing text and screenshots have no ranking claims.
-
targetSdkand Billing Library meet this year's deadline. - Release builds tested from a Play track.