The bundle is fine. Play's rules moved. Here are the exact Gradle values, the plugin version to raise, and the billing trap that forces a Capacitor upgrade.
The Android App Bundle builds and signs without errors. Then Play refuses it with "Play automatic protection requires a minimum SDK version of 24".
You may hit the neighbours of this error in the same week:
Play's requirements moved, and older templates did not. Play now requires minSdk 24 or higher, compileSdk and targetSdk 36, and Play Billing Library 8 if any billing library is present.
Libraries push you too. androidx.browser 1.9.0, which social login plugins pull in, needs compileSdk and targetSdk 36 and Android Gradle Plugin 8.9.1 or newer. The Capacitor 7 template ships 35 and 8.7.2.
Billing is the awkward one. RevenueCat's Capacitor 6 plugin line ends at 9.2.2, which bundles Billing 7.1.1. Every RevenueCat release with Billing 8 requires Capacitor 7. So on Capacitor 6 there is no plugin version that passes.
Step 1. Set the SDK levels. In android/variables.gradle:
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
// keep your other values as they are
}
Step 2. Raise the Android Gradle Plugin. In android/build.gradle:
dependencies {
classpath 'com.android.tools.build:gradle:8.9.1'
}
Gradle wrapper 8.11.1 already supports it. If you are stuck on an older Android Gradle Plugin for now, this line in gradle.properties can get you through:
android.suppressUnsupportedCompileSdk=36
Step 3. Find every billing library. Ask Gradle what the release build really includes:
cd android
./gradlew app:dependencies --configuration releaseRuntimeClasspath | grep -i billing
If you see a billing library you do not use, remove the plugin that brings it in. If nothing matches, billing is not your problem.
Step 4. If you need billing, upgrade to Capacitor 7. Then install the RevenueCat plugin release that matches Capacitor 7 and ships Billing 8. Plan this together with iOS. The upgrade invalidates any iOS build waiting for review, which means building and submitting a new one.
A sensible order: wait for any iOS build in review to clear, upgrade Capacitor and the plugin, sync both platforms, then build and test Android and iOS together. Submit both. While the iOS build is in review, freeze anything the app depends on, because backend changes during review change what the reviewer sees.
Step 5. Bump versionCode. It lives in android/app/build.gradle, not app.json. Play rejects codes it has already seen.
Step 6. Build and read the real error if it fails. Gradle prints the "What went wrong" block well before its last lines. If you only look at the tail you see BUILD FAILED and nothing else. Search the output instead:
./gradlew bundleRelease --no-daemon 2>&1 | grep -A4 "What went wrong"
If the release build runs out of memory with Metaspace errors, raise the Gradle memory in gradle.properties:
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
Step 7. Upload and confirm. Check that the release reached the track in Play Console, not just that the upload returned success.
com.google.android.gms.permission.AD_ID into your manifest. Remove it with tools:node="remove" and answer No.set -o pipefail in bash scripts.Every entry follows the same pattern: the symptom you see, the real cause, the fix that worked, and the rule that stops it coming back. App Store, Google Play, Capacitor, Netlify, Supabase, Stripe, n8n and more.
Get Field NotesBuilding a lot? The Builder Bundle has Field Notes plus the launch playbook, the prompt book and the Claude Code skills kit. See the bundle.
You said the app does not use the advertising ID.
It works on your machine.
Every API call in the app fails.
What broke and how we fixed it, across app stores, hosting, databases and payments.