Google Play closed testing for React Native and Expo apps
The rule is the same for every framework. A personal Play Console account created after November 13, 2023 must run a closed test with at least 12 testers opted in for 14 continuous days before it can apply for production access (Play Console Help).
React Native gives you two ways to get there: the React Native CLI with Gradle, or Expo with EAS Build and EAS Submit. They differ on where the target SDK lives, who holds your keystore, and which track your first upload lands on. That last one catches a lot of Expo users. The closed testing guide covers the rule itself.
Build the AAB
Since August 2021, new apps must publish on Google Play as Android App Bundles (Android Developers). Both paths produce one.
React Native CLI (React Native docs):
npx react-native build-android --mode=releaseIt runs Gradle's bundleRelease and writes android/app/build/outputs/bundle/release/app-release.aab.
Expo (Expo docs):
eas build --platform android --profile productionThe default production profile produces an .aab. A profile only produces an .apk when it sets android.buildType to apk, and an .apk can't be submitted to Google Play. EAS Build works on Expo's free plan; paid plans mainly reduce queue time (Expo docs).
Target API 36: where to look
Since August 31, 2026, new apps and updates must target Android 16 (API level 36) or higher (Play Console Help).
React Native CLI. The value lives in your project's android/build.gradle, inside buildscript { ext { ... } }. On the 0.88-stable branch of the official template (0.88 is the latest version listed on reactnative.dev/versions today), it reads (template):
minSdkVersion = 24
compileSdkVersion = 37
targetSdkVersion = 36This file belongs to your project. Bumping the react-native package doesn't rewrite it. If your project started on an older template, open the file and check the number yourself.
Expo. The SDK version decides. Expo's table lists SDK 56, 57 and 58, and all three use targetSdkVersion 36 (SDK 58 compiles against 37 and pairs with React Native 0.88) (Expo docs). Older SDKs are no longer in the table. If you are on one, upgrade before the closed test rather than patching around it.
The target API 36 guide lists what to retest after the bump.
Signing and who holds the key
React Native CLI. Generate an upload key with keytool, put the MYAPP_UPLOAD_* variables in ~/.gradle/gradle.properties (not android/gradle.properties, so they stay out of git), and add a release signing config in android/app/build.gradle (React Native docs). The same page notes that Google Play only accepts the AAB if Play App Signing is configured for the app.
Expo. EAS CLI can generate the keystore for you and stores it on Expo's servers (Expo docs). eas credentials shows the configured credentials and can sync them to your machine (Expo docs). Do that once and back the file up.
Why it matters now: with Play App Signing, Google can reset a lost upload key, but a self-managed app signing key can't be reset (Play Console Help). Android developer verification adds a second reason. Registering a package can require proof that you own its signing key, and Google's FAQ says a lost signing key means you can't register your packages (Android Developers FAQ).
One more trap: Google Sign-In and Firebase need the fingerprint of the Google-held app signing key, not only your upload key (Play Console Help). Otherwise login works in your local build and fails for testers who installed from Google Play.
The Expo trap: your first submit goes to internal testing
Expo's docs say that on a first submission, the default eas submit "creates your app's first release on the internal testing track" (Expo docs). It needs a Google service account key uploaded to Expo first.
Internal testing doesn't satisfy Google's requirement. Worse, Google says a user who opts into your internal test is no longer eligible to receive your closed test (Play Console Help). If you send your future closed testers the internal link, they can't join the closed test until they opt out of internal. The internal vs closed vs open guide explains the difference.
What to do instead:
- Upload the first closed release yourself in Play Console: Test and release > Testing > Closed testing > Manage track > create release, and pick the
.aabfrom your EAS build. - Then automate. The
trackfield in youreas.jsonsubmit profile acceptsproduction,beta,alphaandinternal(Expo docs). In Google's Play Developer API,betais the open testing track (Google for Developers), which stays locked until you have production access. Confirm the track name of your closed test in Play Console before you rely on a value. - If your app is still a draft in Play Console, set
releaseStatustodraftin the submit profile and finish the release in Play Console.
Version codes
Every upload needs a higher versionCode (Play Console Help). You will ship fixes during the 14 days, so automate it.
- React Native CLI:
versionCodeindefaultConfigofandroid/app/build.gradle. - Expo:
android.versionCodein your app config, "Increment by one for each release" (Expo docs). Or setautoIncrementin the build profile; withcli.appVersionSourceset toremote, Expo's servers keep the counter (Expo docs).
Permissions you didn't ask for
React Native adds the INTERNET permission by default. SYSTEM_ALERT_WINDOW is added in debug only and removed in production (React Native docs).
Native libraries merge their own permissions into the final manifest. In Expo, android.blockedPermissions removes permissions that packages add; in a bare project, use tools:node="remove" in AndroidManifest.xml (Expo docs). It matters because Google evaluates high-risk permissions on test tracks too, and an undeclared one on an active closed release can block you from publishing changes (Play Console Help).
Release-only bugs testers will find
- Blank or crashing release build. React Native's docs warn that
org.gradle.configureondemand=trueingradle.propertiesmakes the release build skip bundling JS and assets (React Native docs). Remove it. - Shrinking. If you enable Proguard, each native library may need its own rules. Test the shrunk build.
- Test the real artifact. Uninstall the debug version, then run
npm run android -- --mode="release"on a phone. For Expo, install the production build itself, not a development client. - Login walls. Give testers working credentials, and fill in App access in Play Console for Google's reviewers.
The closed testing setup guide walks through the Testers tab, countries and the opt-in link.
How long it takes
- RN CLI signing setup: about an hour if Java and Android Studio already work.
- EAS: Expo says a small app's build triggers within a few minutes; paid plans mainly cut queue time. Signing is a few prompts.
- Google's first review: for some new accounts, up to seven days or longer in exceptional cases (Play Console Help). Then the opt-in link can take several hours.
- The test: 14 continuous days. Production access review: usually seven days or less (Play Console Help).
Plan three to four weeks from the first closed upload to production.
Frequently asked questions
Do React Native and Expo apps need 12 testers for 14 days?
Yes, if your Play Console account is a personal account created after November 13, 2023. The rule is about your account, not your framework.
Which Expo SDK targets API level 36?
Expo's SDK table, checked on November 8, 2026, lists SDK 56, 57 and 58, and all three target Android API 36. If your project uses an older SDK, upgrade before you build for the closed test.
Where is targetSdkVersion set in a React Native CLI project?
In android/build.gradle, in the buildscript ext block. The React Native 0.88 template sets targetSdkVersion = 36. Older projects keep whatever value they had until you change it.
Why did eas submit put my app on internal testing?
Expo's docs say the default eas submit creates your app's first release on the internal testing track. Internal testing doesn't count for Google's requirement, and testers who opt into internal can't receive your closed test.
My React Native release build crashes on launch but debug works. What should I check first?
React Native's docs warn that org.gradle.configureondemand=true in gradle.properties makes the release build skip bundling JavaScript and assets. Remove it, rebuild, and test the release build on a real phone.
Sources
- support.google.com/googleplay/android-developer/answer/14151465
- support.google.com/googleplay/android-developer/answer/9845334
- support.google.com/googleplay/android-developer/answer/11926878
- support.google.com/googleplay/android-developer/answer/9842756
- support.google.com/googleplay/android-developer/answer/9214102
- support.google.com/googleplay/android-developer/answer/17367361
- developer.android.com/developer-verification/guides/faq
- developers.google.com/android-publisher/tracks
- developer.android.com/guide/app-bundle
- support.google.com/googleplay/android-developer/answer/9859751
- reactnative.dev/docs/signed-apk-android
- reactnative.dev/versions
- github.com/react-native-community/template/blob/0.88-stable/template/android/build.gradle
- docs.expo.dev/versions/latest
- docs.expo.dev/submit/android
- docs.expo.dev/eas/json
- docs.expo.dev/build/setup
- docs.expo.dev/app-signing/managed-credentials
- docs.expo.dev/versions/latest/config/app