Automate this: /mobile-release:ship-beta runs this skill end to end and uploads the result;
/mobile-release:bootstrap creates the keystore, signingConfigs, and CI workflow from nothing.
Keystore creation is a gated step — see skills/release-automation for what an agent may do unattended.
Produce a signed, verified Android release artifact from a source tree. Stop at a verified artifact on disk — this skill does not upload to Play.
Process:
- Discover the project — wrapper, modules, variants, artifact paths — using Discover the project below.
- Set
versionCodeandversionNameusingreferences/versioning.md.versionCodemust be a strictly higher integer than every code already uploaded for this package, on any track. - Confirm an upload keystore exists. If it does not, create one using Create the upload keystore below, and keep it outside the repository.
- Wire
signingConfigsinto thereleasebuild type usingreferences/signing-configs.md— Kotlin DSL and Groovy, and the four ways to supply the credentials. - Confirm the target API level satisfies Target API level below before building.
- Build with
./gradlew bundleRelease, and collect the artifact, the R8 mapping file, and native debug symbols perreferences/build-outputs.md. - Verify the artifact with
references/verification.md—bundletoolfor an AAB,apksigner verify --print-certsfor an APK. Never report a build as done without a certificate fingerprint. - If Play App Signing is in play — first release, lost upload key, key rotation — read
references/play-app-signing.md. - To run this in CI, use the workflow in
references/github-actions.md. - On any failure, look the error up in
references/troubleshooting.mdbefore changing the build.
Load only the reference a step actually needs.
Contents
- Discover the project
- Create the upload keystore
- Target API level
- Build
- Confidence and freshness
- Output Format
Discover the project
# Always drive the wrapper, never a system `gradle` — the wrapper pins the Gradle version.
ls gradle/wrapper/gradle-wrapper.properties && ./gradlew --version
# If gradlew is not executable (repo authored on Windows):
chmod +x ./gradlew
# Modules
./gradlew projects
# Every task on the application module, including per-variant assemble*/bundle* tasks
./gradlew :app:tasks --all
# Just the variant-producing tasks
./gradlew :app:tasks --all | grep -iE '^(assemble|bundle)[A-Z]'
Read, in this order: settings.gradle[.kts] (module list), gradle/libs.versions.toml or the root
build file (AGP and Kotlin versions — they decide the required JDK), and the application module's
build.gradle[.kts] (applicationId, versionCode, versionName, minSdk/targetSdk/compileSdk,
signingConfigs, buildTypes, productFlavors, isMinifyEnabled).
If Google's Android CLI is installed, use it to locate artifacts instead of guessing paths:
command -v android && android describe --project_dir=.
android describe emits JSON naming the project's build targets and their output artifact
locations. The CLI has no signing and no publishing verbs — everything below still applies.
See the android-cli skill in this plugin for installation and the rest of its commands.
Create the upload keystore
mkdir -p ~/.android-keystores
keytool -genkeypair -v \
-keystore ~/.android-keystores/myapp-upload.jks \
-storetype PKCS12 \
-alias upload \
-keyalg RSA -keysize 2048 \
-validity 10000 \
-dname "CN=My App, OU=Mobile, O=My Company, L=Berlin, C=DE"
keytool then prompts for the store password. Pass -storepass/-keypass only when the value comes
from a secret manager in a script — on a command line they land in shell history and the process table.
Export the certificate to register with Play:
keytool -export -rfc \
-keystore ~/.android-keystores/myapp-upload.jks \
-alias upload \
-file upload-certificate.pem
Rules:
- The keystore never goes in the repository. This plugin's
.gitignoreblocks*.jks,*.keystore,keystore.properties, and*.pem, but that only stops the commit — a keystore in a working tree is still a keystore on a shared machine. Keep it in~/.android-keystores/, a password manager, or a CI secret, and nowhere else. -validity 10000is about 27 years. Play requires the key to be valid until at least October 22, 2033; 25+ years is recommended.- Back up the file and both passwords in two independent places. A lost upload key is recoverable
through Play App Signing (
references/play-app-signing.md); a lost app signing key on an app not enrolled in Play App Signing is not recoverable at all — the app must be republished under a new package name. -genkeypairis the current spelling. Google's docs still show the older-genkeyalias; the two are identical.
Target API level
From https://developer.android.com/google/play/requirements/target-sdk:
Starting August 31, 2026, new apps and app updates must target:
| Form factor | Minimum target API |
|---|---|
| Phone / tablet / default | Android 16 — API level 36 |
| Wear OS | Android 15 — API 35 |
| Android Automotive OS | Android 15 — API 35 |
| Android TV | Android 14 — API 34 |
| Android XR | Android 14 — API 34 |
Existing published apps that are not being updated must target Android 15 (API 35) or higher to stay available to new users on devices running an OS newer than the app's target. Apps targeting API 34 or lower are only discoverable and installable on devices running the same or a lower Android OS version. Exceptions to that existing-app rule: Wear OS, Android TV, and Android XR at API 33 or lower; Android Automotive OS at API 31 or lower.
An extension to November 1, 2026 can be requested per app through a form in Play Console.
For a phone/tablet app shipping now, set targetSdk = 36 and compileSdk = 36.
Build
./gradlew clean bundleRelease # .aab — what Play wants
./gradlew clean assembleRelease # .apk — sideloading, other stores, device smoke tests
Every app created after August 2021 must publish an app bundle, and publishing an AAB requires
Play App Signing. Reach for assembleRelease only for distribution outside Play or for local
testing. Flavored projects use the variant name: ./gradlew :app:bundleProdRelease.
Output paths, R8/minifyEnabled and the mapping file, and ndk.debugSymbolLevel for native symbols
are in references/build-outputs.md.
Confidence and freshness
Research date for this skill: 2026-08-05.
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.
Unlike its siblings, this skill carries almost no marks, because almost everything in it was read
off a first-party source: developer.android.com for the target-SDK requirements and the AGP,
keytool, apksigner, zipalign and bundletool documentation, and Play Console Help for the
signing and upload rules. The one claim that could not be pinned down is flagged in place — see the
AGP-version caveat at the end of references/build-outputs.md.
Nothing here was executed: the authoring environment had no Android SDK build-tools. Treat the first real run as the test, and prefer what the tool prints over what this page says.
Output Format
Report the artifact, the identity it was signed with, and the numbers someone needs before uploading. Never claim a build succeeded without a certificate fingerprint. State plainly what was not done.
Example output:
Release build — com.example.myapp
Artifact: app/build/outputs/bundle/release/app-release.aab — 18.4 MB on disk
Estimated download: 11.9 MB min / 14.2 MB max (bundletool get-size total)
versionCode: 412 (previous upload was 411)
versionName: 3.7.0
Variant: release — minifyEnabled = true, shrinkResources = true, compileSdk/targetSdk 36, minSdk 24
Signing — keytool -printcert -jarfile on the bundle:
Owner: CN=My App, OU=Mobile, O=My Company, L=Berlin, C=DE
SHA-1: 8F:2B:6C:1D:4A:90:33:E7:B5:0C:22:AE:71:98:4D:F0:3C:6B:11:52
SHA-256: 3A:1F:...:C9:2E
Valid until: Fri Dec 21 11:04:18 CET 2052
This is the upload certificate registered at Play Console → Release → Setup → App signing, not the app signing certificate — Google strips this signature and re-signs the delivered APKs.
Kept alongside the artifact (both must be uploaded with this versionCode):
app/build/outputs/mapping/release/mapping.txt— 1.2 MBapp/build/outputs/native-debug-symbols/release/native-debug-symbols.zip— 44 MB
Device check: built with bundletool build-apks --connected-device and installed with
bundletool install-apks on a Pixel 8 / API 35. App launched, no crash.
Not done: nothing was uploaded to Play, and no track was touched. The keystore stayed at
~/.android-keystores/myapp-upload.jks and was not copied into the repository.
End of example.