SuperAPI AI Directory

play-store-publish

Deployment & operations

View repository

Updated 10-16 18:29

About this skill

Automate this: /mobile-release:ship-beta uploads to the internal track; /mobile-release:ship-release handles production and the staged-rollout decision, gating on approval before anything irreversible. What is safe to re-run: skills/release-automation/references/idempotency.md.

Upload signed Android artifacts to Google Play and move them between tracks. Execute the steps in order. Load only the reference you need.

Confidence marks used throughout this skill and its references: [verified] — read off a primary vendor source on 2026-08-05. [snippet] — secondary source only, plausible but unconfirmed. [unverified] — not confirmed anywhere; run the check given before relying on it.

Publish process:

  1. Confirm the artifact is a signed .aab and that the release is buildable. Do not attempt an upload against an unsigned or debug-signed bundle.
  2. Set up and verify service-account authentication using references/service-account.md.
  3. Pick one upload path and stay on it: fastlane supply (references/fastlane-supply.md), raw Publishing API v3 (references/publishing-api-v3.md), or the r0adkll/upload-google-play GitHub Action (references/github-actions.md).
  4. Choose the track, release status, and rollout fraction using references/tracks-and-rollout.md.
  5. Upload the R8/ProGuard mapping and native debug symbols using references/mapping-and-symbols.md.
  6. Commit the edit. Nothing is live until commit succeeds.
  7. Verify the release landed on the intended track. On any failure, diagnose with references/troubleshooting.md before retrying — do not blindly re-run.

Before publishing anything to a public track, run the play-store-listing skill. Upload succeeds long before the listing is compliant.

Contents

  • Prerequisites
  • Choosing an upload path
  • Tracks
  • Mapping and native symbols
  • Troubleshooting
  • Core Instructions
  • Output Format
  • References

Prerequisites

  1. Google Cloud project with the Google Play Android Developer API enabled.

  2. Service account in that project, with a downloaded JSON key (IAM → Service Accounts → Keys → Add key → JSON).

  3. Play Console access granted to the service-account email: Users and permissions → Invite new users, add the …iam.gserviceaccount.com address, grant app-level access plus release permissions. Grants take minutes to propagate; a brand-new service account 401s or 403s at first.

  4. OAuth scope. There is exactly one, verified from the live androidpublisher:v3 discovery document:

    https://www.googleapis.com/auth/androidpublisher
    

    Its description is "View and manage your Google Play Developer account". Base URL is https://androidpublisher.googleapis.com/.

  5. Verify the key before doing anything else:

    fastlane run validate_play_store_json_key json_key:fastlane/play-store-key.json
    

    If that fails, fix auth first. Every downstream error is misleading until it passes.

  6. Never commit the JSON key. Store it as a CI secret and add it to .gitignore.

Full walkthrough plus the permission-propagation failure modes: references/service-account.md.

Choosing an upload path

Path Use when Reference
fastlane supply The repo already uses fastlane, or you need to sync full multi-locale metadata, screenshots, and changelogs alongside the binary references/fastlane-supply.md
Publishing API v3 direct No Ruby toolchain, or you need control the wrappers do not expose (named tracks, countryTargeting, custom retention of version codes) references/publishing-api-v3.md + references/upload_aab.py + references/upload_aab.sh
r0adkll/upload-google-play GitHub Actions CI and you want the least moving parts references/github-actions.md

All three drive the same transactional model: insert an edit, change things, commit the edit.

Tracks

Track identifiers as used by the API and by supply:

Track API name Audience
Internal testing internal Small tester list, fastest propagation
Closed testing alpha (default closed track; named closed tracks also exist) Email lists / Google Groups
Open testing beta Anyone, listed on Play
Production production Everyone

Promotion moves an existing versionCode to another track — it does not re-upload the binary. Staged rollout is status: "inProgress" plus a userFraction between 0 and 1 exclusive (a double: 0.1 means 10%, not 10). Halt with status: "halted", resume by setting inProgress again with a fraction, finish with status: "completed".

Two schema facts that bite:

  • versionCodes on a TrackRelease "must include version codes to retain from previous releases" — omitting older codes de-lists them.
  • inAppUpdatePriority is an integer 0–5 (5 highest, default 0) and cannot be updated once the release is rolled out. Set it on the first write or not at all.
  • countryTargeting is "only allowed to be set for inProgress releases in the production track".

Details, promotion commands, and full TrackRelease semantics: references/tracks-and-rollout.md.

Mapping and native symbols

Upload the deobfuscation file for every uploaded versionCode, in the same edit as the bundle:

POST /androidpublisher/v3/applications/{packageName}/edits/{editId}/apks/{apkVersionCode}/deobfuscationFiles/{deobfuscationFileType}

deobfuscationFileType is proguard for the R8/ProGuard mapping.txt and nativeCode for a zip of unstripped .so files. Transport cap is 1 677 721 600 bytes. Without these, Play's crash reports stay obfuscated and native crashes stay unsymbolicated. See references/mapping-and-symbols.md.

Troubleshooting

Symptom Cause Fix
"signed with the wrong key" / "not signed" Bundle signed with a key Play does not recognise as the upload key Compare keytool -printcert -jarfile app.aab against Play Console → Release → Setup → App signing. If the upload key is lost, request an upload key reset — the app signing key is unaffected
"Version code N has already been used" versionCode collides with any previously uploaded artifact, including deleted drafts Bump versionCode. Version codes are never reusable
"Only releases with status draft may be created on draft app" The app has never had a live release, so Play only accepts draft releases Set the release status to draft, commit, then finish the listing and content declarations and publish once from the Console. Afterwards completed/inProgress work
"Track … could not be found" Named closed track does not exist yet Create it with edits.tracks.create, or create it in the Console first. Default names are internal, alpha, beta, production
401 Invalid credentials / 403 insufficient permissions Service account not invited in Play Console, missing app-level access, API not enabled, or grant not yet propagated Re-run validate_play_store_json_key, confirm the invite, wait a few minutes
"Changes cannot be sent for review automatically" Pending Console changes or an app that has never been reviewed Set changesNotSentForReview on commit, then submit from the Console
Edit rejected as expired/committed Edits are transactional and short-lived; one edit cannot be committed twice Insert a fresh edit and redo the operations

Full error strings and the diagnostic order: references/troubleshooting.md.

Core Instructions

  • The AAB is mandatory for new apps (since August 2021) and for TV apps (since June 2023). Play App Signing is required with it. Do not propose an APK upload path for a new app.
  • Sign an .aab with jarsigner. apksigner is for .apk only.
  • Check size before uploading: bundletool get-size total --apks=app.apks. A single install's total compressed download is capped at 4 GB.
  • Google's own advice: "Don't publish alpha/beta updates more than once daily (production even less frequently)."
  • Never print the contents of the service-account JSON key into the transcript or into CI logs.
  • Report the resulting editId, versionCode, track, and status after every upload.

Output Format

Report the release as a short status block, then the next action. Do not narrate every HTTP call.

Example output:

Play upload — com.example.app

Field Value
Path Publishing API v3 (references/upload_aab.py)
Artifact app/build/outputs/bundle/release/app-release.aab (48.2 MB)
versionCode 143
versionName 2.7.0
Track internal
Status completed
Mapping uploaded (proguard, 6.1 MB)
Native symbols uploaded (nativeCode, 22.4 MB)
Edit 07714948113946731254 — committed

Retained version codes: 141, 142, 143 — passed explicitly so the previous internal builds stay installable.

Next: promote to production at 10% once internal QA signs off:

python3 references/upload_aab.py promote \
  --package com.example.app \
  --key play-store-key.json \
  --version-code 143 \
  --track production \
  --status inProgress \
  --user-fraction 0.1

Warning: inAppUpdatePriority was left unset (defaults to 0) and cannot be changed after rollout begins. If this release needs a forced update prompt, halt now and re-create the release with --in-app-update-priority 3.

End of example.

References

  • references/service-account.md — Cloud project, service account, JSON key, Play Console invite, scope, verification, and permission failure modes.
  • references/fastlane-supply.md — supply init, the exact metadata/android/<locale>/ tree, changelogs, screenshots, option table, upload ordering, and a complete Fastfile with internal, beta, production-staged, and promote lanes.
  • references/publishing-api-v3.md — the edit lifecycle, every edits.* endpoint with HTTP method and path, upload size caps, and a step-by-step curl walkthrough.
  • references/upload_aab.py — working Python uploader/promoter (google-auth + requests).
  • references/upload_aab.sh — bash + curl equivalent.
  • references/tracks-and-rollout.md — tracks, promotion, TrackRelease schema, staged rollout, halting and resuming, inAppUpdatePriority, countryTargeting.
  • references/github-actions.md — complete r0adkll/upload-google-play workflow, input table, secret handling.
  • references/mapping-and-symbols.md — R8 mapping and native debug symbol upload across all three paths.
  • references/troubleshooting.md — error strings, causes, fixes, and the order to check them in.