Skip to content

Google Play closed testing for React Native and Expo apps

Updated

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):

bash
npx react-native build-android --mode=release

It runs Gradle's bundleRelease and writes android/app/build/outputs/bundle/release/app-release.aab.

Expo (Expo docs):

bash
eas build --platform android --profile production

The 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):

groovy
minSdkVersion = 24
compileSdkVersion = 37
targetSdkVersion = 36

This 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:

  1. Upload the first closed release yourself in Play Console: Test and release > Testing > Closed testing > Manage track > create release, and pick the .aab from your EAS build.
  2. Then automate. The track field in your eas.json submit profile accepts production, beta, alpha and internal (Expo docs). In Google's Play Developer API, beta is 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.
  3. If your app is still a draft in Play Console, set releaseStatus to draft in 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: versionCode in defaultConfig of android/app/build.gradle.
  • Expo: android.versionCode in your app config, "Increment by one for each release" (Expo docs). Or set autoIncrement in the build profile; with cli.appVersionSource set to remote, 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=true in gradle.properties makes 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

  1. support.google.com/googleplay/android-developer/answer/14151465
  2. support.google.com/googleplay/android-developer/answer/9845334
  3. support.google.com/googleplay/android-developer/answer/11926878
  4. support.google.com/googleplay/android-developer/answer/9842756
  5. support.google.com/googleplay/android-developer/answer/9214102
  6. support.google.com/googleplay/android-developer/answer/17367361
  7. developer.android.com/developer-verification/guides/faq
  8. developers.google.com/android-publisher/tracks
  9. developer.android.com/guide/app-bundle
  10. support.google.com/googleplay/android-developer/answer/9859751
  11. reactnative.dev/docs/signed-apk-android
  12. reactnative.dev/versions
  13. github.com/react-native-community/template/blob/0.88-stable/template/android/build.gradle
  14. docs.expo.dev/versions/latest
  15. docs.expo.dev/submit/android
  16. docs.expo.dev/eas/json
  17. docs.expo.dev/build/setup
  18. docs.expo.dev/app-signing/managed-credentials
  19. docs.expo.dev/versions/latest/config/app