Google Play closed testing for Kotlin and Kotlin Multiplatform apps
Native Kotlin doesn't change Google's rule. If your Play Console account is a personal account created after November 13, 2023, you must run a closed test with at least 12 testers opted in for 14 continuous days before you can apply for production access (Play Console Help).
You're already on the platform's own toolchain, so there's no framework layer between you and Gradle. The traps are Gradle ones: an Android Gradle plugin (AGP) too old for API 36, a release build that only breaks once R8 runs, and, for Kotlin Multiplatform and Compose Multiplatform, the AGP 9 change to how Android modules are declared. For the rule and every Play Console screen, start with the Google Play closed testing guide.
Step 1: targetSdk 36, and the AGP that supports it
Since August 31, 2026, new apps and app updates must target Android 16 (API level 36) or higher. Wear OS and Android Automotive OS apps need API 35, Android TV and Android XR apps API 34 (Play Console Help).
The value is in your app module's build.gradle.kts, in defaultConfig, next to applicationId, minSdk, versionCode and versionName (Android Developers):
android {
defaultConfig {
applicationId = "com.yourcompany.yourapp"
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
}Many projects read the number from gradle/libs.versions.toml instead. Check where yours comes from.
Then check your tools. Google publishes the minimum versions per API level (Android Developers):
| API level | Minimum Android Studio | Minimum AGP |
|---|---|---|
| 37 | Panda 3 2025.3.3 Patch 1 | 9.1.1 |
| 36.1 | Narwhal 3 Feature Drop 2025.1.3 | 8.13.0 |
| 36 | Meerkat 2024.3.1 Patch 1 | 8.9.1 |
| 35 | Koala Feature Drop 2024.2.1 | 8.6.0 |
Google warns that lower versions "could lead to unexpected issues". Checked on November 9, 2026, the latest stable release is AGP 9.5 (October 2026), which supports up to API 37 (Android Developers). The target API 36 guide lists the behavior changes to retest.
Step 2: Fix the application ID before the first upload
Replace com.example.* now. Google says that once you upload an artifact, the package name is fixed and cannot be changed (Play Console Help). The application ID needs at least two segments, and each must start with a letter (Android Developers).
Step 3: Build a signed AAB
In Android Studio: Build > Generate Signed Bundle/APK, choose Android App Bundle, then create or select your upload keystore. Google's guidance for the key: a validity of at least 25 years, and the same password for the key as for the keystore because of a known issue (Android Developers).
From the command line: add your signing information to the app module's build.gradle.kts, then run the bundle task of the release variant, for example ./gradlew :app:bundleRelease. Gradle builds and signs the bundle. If you sign as a separate step, use jarsigner: Google says you can't use apksigner on an app bundle (Android Developers). Keep keystore passwords out of the build file you commit.
With Play App Signing, Google holds the app signing key and you hold the upload key. Google can reset a lost upload key; an app signing key you manage yourself can't be reset (Play Console Help).
Google Sign-In and Firebase. Google signs the APKs your testers receive, so register the app signing key's fingerprint from Play Console with your API providers, not only your upload key (same source). The Gradle signingReport task shows your local fingerprints (Android Developers).
Step 4: versionCode goes up with every upload
versionCode is the internal number Google Play compares; versionName is what users see. Each release needs a greater versionCode, you can't reuse one, and Google Play's maximum is 2,100,000,000 (Android Developers). You'll ship fixes during the 14 days: raise it by one each time, or derive it from your CI build number.
Step 5: Test the R8 build, not only debug
Google recommends enabling app optimization only for the release build (Android Developers), so R8 problems only show up there:
- AGP 9.3 and higher: set
enable = truein anoptimization {}block of the release build type. Keep rules go in.keepfiles undersrc/<variant>/keepRules. - Earlier AGP:
isMinifyEnabled = trueandisShrinkResources = true, withgetDefaultProguardFile("proguard-android-optimize.txt"). Since AGP 9.0,proguard-android.txtis no longer supported.
R8 full mode is on by default since AGP 8.0 and makes stricter assumptions about reflection. Libraries that use reflection or serialization by class name may need keep rules. Install the release build on a real phone and go through sign-in, payments and any screen that loads data.
Step 6: Check the merged manifest
Libraries add their own permissions to the final manifest. Before uploading, open AndroidManifest.xml in Android Studio and click the Merged Manifest tab at the bottom of the editor. To drop a permission a library adds, use tools:node="remove" on it in your own manifest (Android Developers). Google evaluates high-risk permissions such as SMS or Call Log on test tracks too, and an undeclared one on an active closed release can block you from publishing changes (Play Console Help).
Kotlin Multiplatform and Compose Multiplatform
JetBrains is clear that the Android side isn't special: the shared module's Android library and the Android app "are typical Android Gradle modules", so publishing "is no different from the usual process" (Kotlin docs). Steps 1 to 6 apply. Three KMP-specific points:
Where targetSdk lives. JetBrains' KMP App Template keeps it in gradle/libs.versions.toml as android-targetSdk. On its main branch, checked on November 9, 2026, that value is 37, with AGP 9.1.1 (template). An older project generated by the wizard keeps whatever value it was created with.
AGP 9 splits the app module out. From AGP 9.0, the Kotlin Multiplatform plugin is no longer compatible with com.android.application and com.android.library (Kotlin docs). JetBrains' migration:
- Move the Android entry point (
MainActivity) out of the shared module (oftencomposeApp) into a newandroidAppmodule that appliescom.android.application. - Move
applicationId,targetSdk,versionCodeandversionNamewith it. They no longer belong in the shared module. - Switch the shared module to the Android-KMP library plugin.
As a stopgap, android.enableLegacyVariantApi=true in gradle.properties keeps the old setup working on AGP 9. JetBrains says those APIs will be removed in AGP 10.
Build the right module. After the split, the AAB you upload comes from androidApp, not from the shared module. Run its release bundle task.
Step 7: Release on the closed testing track
In Play Console, go to Test and release > Testing > Closed testing, then Manage track. Create a release, upload the .aab, add testers in the Testers tab, pick countries, and send the changes for review.
The opt-in link only shows once the app status is Published, and the first time it can take several hours to work (Play Console Help). If it's missing, see where to find the opt-in link. Keep your 12 closed testers off the internal track: Google says a user who opts into your internal test can't receive your closed test.
How long it really takes
- Native Kotlin app with a recent AGP: under an hour to a signed AAB, plus the time to test the R8 build.
- KMP project still on AGP 8 that you move to AGP 9: plan half a day to a day for the module split.
- First closed release: Google can take up to seven days, longer in exceptional cases, for some accounts (Play Console Help).
- The test: 14 continuous days. Production access review: usually seven days or less (Play Console Help).
Count on three to four weeks between your first closed upload and a production release.
Frequently asked questions
Do native Kotlin apps need 12 testers for 14 days on Google Play?
Yes, if your Play Console account is a personal account created after November 13, 2023. Writing the app in Kotlin with Android Studio doesn't change the rule: a closed test with at least 12 testers opted in for 14 continuous days.
Which Android Gradle plugin version do I need for targetSdk 36?
Google's table says API level 36 needs at least Android Studio Meerkat 2024.3.1 Patch 1 and AGP 8.9.1. Lower versions can lead to unexpected issues.
Is publishing a Kotlin Multiplatform app to Google Play different?
JetBrains says no: the Android app from a KMP project is a typical Android Gradle module, so you publish it the usual way. The difference is in the build setup, especially with AGP 9.
Why does my KMP project break with AGP 9?
JetBrains says that from AGP 9.0, the Kotlin Multiplatform plugin is no longer compatible with com.android.application and com.android.library in the same module. Move the Android entry point to its own app module and use the Android-KMP library plugin for shared code.
My release build crashes but debug works. What should I check?
R8. Google recommends enabling optimization only for the release build, so code removed or renamed by R8 only breaks there. Add keep rules for code reached by reflection and test the release build on a phone.
Sources
- support.google.com/googleplay/android-developer/answer/14151465
- support.google.com/googleplay/android-developer/answer/11926878
- support.google.com/googleplay/android-developer/answer/9845334
- support.google.com/googleplay/android-developer/answer/9842756
- support.google.com/googleplay/android-developer/answer/17367361
- support.google.com/googleplay/android-developer/answer/9214102
- support.google.com/googleplay/android-developer/answer/9859751
- developer.android.com/build/releases/about-agp
- developer.android.com/build/releases/gradle-plugin
- developer.android.com/build/configure-app-module
- developer.android.com/studio/publish/app-signing
- developer.android.com/build/building-cmdline
- developer.android.com/studio/publish/versioning
- developer.android.com/build/shrink-code
- developer.android.com/build/manage-manifests
- kotlinlang.org/docs/multiplatform/multiplatform-publish-apps.html
- kotlinlang.org/docs/multiplatform/multiplatform-project-agp-9-migration.html
- github.com/Kotlin/KMP-App-Template/blob/main/gradle/libs.versions.toml