Skip to content

Setup guides · Expo

Google Play closed testing for Expo (EAS) apps

For Expo projects built with EAS Build: produce a production .aab, let EAS keep the keystore, get the bundle into the closed track and keep 12 testers opted in for 14 days. Bare React Native without EAS? See the React Native guide.

Build
eas build -p android --profile production
Version code
eas.json → autoIncrement
Target API
targetSdkVersion ≥ 36

Build a production app bundle with EAS

  1. Set the package name

    In app.json (or app.config.js), set android.package, for example com.yourcompany.app. It's permanent after the first upload.

  2. Install and configure EAS

    Install the CLI, log in and create eas.json with the commands below. The generated file already has a production profile.

  3. Build

    Run the production build. The default production profile creates an Android App Bundle on EAS servers; add --local to build on your own machine instead.

  4. Download the bundle

    When the build finishes, download the .aab from the build page on expo.dev or from the link in your terminal.

Terminal
npm install -g eas-cli
eas login
eas build:configure
Terminal
eas build -p android --profile production

Don't set android.buildType to apk in the production profile. An APK can't be submitted to Google Play.

Credentials: let EAS keep the upload key

On the first Android build, EAS offers to generate a keystore and stores it on Expo's servers; later builds reuse it. For Google Play this keystore is your upload key. When you upload the first bundle, Play App Signing is set up and Google keeps the app signing key.

Use eas credentials to see the configured keystore, download a backup or replace it. Keep that backup somewhere safe: you'll need it if you ever build locally or move away from EAS.

Terminal
eas credentials -p android

Don't generate a new keystore after your first upload. A bundle signed with a different upload key is rejected until you request an upload key reset in Play Console.

Version code and target API level

Play Console rejects any bundle whose versionCode was already used. The simplest setup lets EAS manage it remotely and increments it on every production build:

eas.json
{
  "cli": { "appVersionSource": "remote" },
  "build": {
    "production": { "autoIncrement": true }
  },
  "submit": {
    "production": {
      "android": { "track": "alpha", "releaseStatus": "draft" }
    }
  }
}

With appVersionSource set to remote, EAS stores the version code and ignores android.versionCode in your app config. If you already uploaded builds by hand, run eas build:version:set once so EAS continues from your last number.

Since 31 August 2026, new apps and updates must target Android 16 (API level 36). Each Expo SDK release sets the target SDK for you, so the reliable fix is an SDK version that targets 36. The expo-build-properties config plugin can override targetSdkVersion, but only do that if your SDK and libraries support it.

Expo pitfalls

  • eas submit goes to internal testing. By default eas submit -p android sends the build to the internal track, which doesn't count for the 12 testers rule. Set "track": "alpha" (Google's default closed track) in the submit profile, as above, or upload the downloaded .aab to the closed track by hand.
  • Draft-only first releases. While the app has never been published, Google only accepts draft releases through the API. Keep "releaseStatus": "draft" and finish the rollout in Play Console.
  • A service account is needed. eas submit uses a Google service account key that you upload to EAS under the app's credentials. Without it, upload manually.
  • Development builds. A build with developmentClient: true expects a dev server. Make sure testers get the production profile.
  • Missing environment variables. Values in a git-ignored .env file aren't uploaded to EAS Build. Define production values as EAS environment variables, or the release build talks to the wrong backend.
  • Native changes need a new build. Permissions, the package name and config plugins only reach testers through a new EAS build, not an over-the-air update.
Terminal
eas submit -p android --profile production

Find out why the build crashes for testers

A production build behaves differently from Expo Go and development builds: there's no dev server, the JavaScript is minified and only the native modules you compiled in are available. Reproduce what testers see before you guess.

  1. Run production mode locally

    npx expo start --no-dev --minify runs your JavaScript the way a release bundle does and shows errors that development mode never throws.

  2. Read native logs

    Connect the phone over USB and run adb logcat. Native logs often reveal the cause when a JavaScript error boundary doesn't catch the problem.

  3. Check Play Console

    Tester crashes appear under Monitor and improve → Android vitals → Crashes & ANRs. The pre-launch report, which Google generates automatically after an upload, also runs the app on test devices and lists the crashes it found.

  4. Make JavaScript errors readable

    With Sentry, EAS Build uploads source maps automatically once SENTRY_AUTH_TOKEN is set as a sensitive EAS environment variable. After eas update, run npx sentry-expo-upload-sourcemaps dist so updates are readable too.

Need native debugging? npx expo prebuild -p android creates the android folder so you can attach Android Studio's debugger. Delete it afterwards to keep the project managed by Expo.

Play Console in short: from closed test to production

Once your Expo bundle is built and signed, the rest happens in Play Console and works the same for every framework. This is the checklist; the full Play Console walkthrough covers every menu and screen.

  • Finish app setup on the Dashboard: privacy policy, app access, ads, content rating, target audience, data safety and store listing.
  • Create a closed testing release under Test and release → Testing → Closed testing: choose your testers' countries, upload the .aab and send it for review.
  • Add testers with a Google Group on the Testers tab and share the opt-in link. It works once the track shows Published.
  • Keep at least 12 testers opted in for 14 days in a row. Internal testing doesn't count, and someone who leaves and rejoins starts their 14 days again.
  • Ship at least one update during the test and write down what testers found and what you changed.
  • Apply for production from the Dashboard and answer the three sections about your test, your app and your readiness. The decision is Google's; nobody can guarantee approval.

Plan for drop-outs: recruit more than 12 testers so one person leaving doesn't push back your timeline. See the full Play Console steps.

The full Play Console walkthrough

Closed track, Google Group and opt-in link, the 12 testers / 14 days rule and the production application, step by step on one page.

Open the Play Console steps

Ship fixes fast: new builds and EAS Update

With remote versioning, run the production build again and upload or submit it to the closed track; the version code goes up on its own.

Terminal
eas build -p android --profile production

For JavaScript-only fixes, EAS Update can deliver a new bundle without a store resubmission. Google Play's Device and Network Abuse policy allows code that runs in an interpreter or virtual machine, such as JavaScript, to be updated this way; it doesn't allow downloading native code. Use updates for real fixes and keep them within what the reviewed build does.

Terminal
eas update --channel production --message "Fix checkout crash" --environment production
  • Channels. eas update:configure sets a channel on the preview and production build profiles. A build only receives updates published to its channel. From SDK 55, --environment is required.
  • Runtime version. An update only reaches builds with the same runtime version. The fingerprint policy changes it whenever something that affects the native runtime changes, so incompatible updates are very unlikely.
  • Restarts. Release builds download an update in the background and apply it after the app restarts. Ask testers to close and reopen the app, up to twice.
  • Native changes need a build. New permissions, config plugins or native libraries reach testers only through a new production build on the closed track, which also leaves a visible release in Play Console.

Frequently asked questions

Should testers use Expo Go?

No. Testers install your app from Google Play through the opt-in link, which means a production EAS build on the closed track. Expo Go and development builds are for you, not for your 12 testers.

Can I put a build on my own phone first?

Yes. Add "buildType": "apk" under android in a preview profile and run eas build -p android --profile preview. An .aab can't be installed directly; it's only for Google Play.

Which runtime version policy should I pick?

appVersion is simple, but if you change native code without bumping the app version, builds and updates stop matching. fingerprint follows native changes automatically at the cost of more builds, which usually suits a short closed test.

What if an update breaks the app?

If an update calls native code the build doesn't have, expo-updates may detect the error and roll back to the previous working update. You can also republish the last stable update on top of the bad one.

Sources

Technical details were checked against the official documentation below. Menus and defaults change between versions; when in doubt, the official page wins.

New to the 12 testers rule?

The general guide explains Google's requirement, how a TestBunny campaign runs day by day and what we can and can't guarantee.

Read the general guide

Need 12 real testers for 14 days?

Add your app and closed testing link, pick Starter (15 testers) or Pro (25 testers), and follow every tester's activity day by day in your panel.

TestBunny isn't affiliated with Google. Testers earn credits, never cash. Nobody can guarantee Google's production access decision, and we don't.