Build a production app bundle with EAS
Set the package name
In
app.json(orapp.config.js), setandroid.package, for examplecom.yourcompany.app. It's permanent after the first upload.Install and configure EAS
Install the CLI, log in and create
eas.jsonwith the commands below. The generated file already has aproductionprofile.Build
Run the production build. The default production profile creates an Android App Bundle on EAS servers; add
--localto build on your own machine instead.Download the bundle
When the build finishes, download the
.aabfrom the build page on expo.dev or from the link in your terminal.
npm install -g eas-cli
eas login
eas build:configureeas build -p android --profile productionDon'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.
eas credentials -p androidDon'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:
{
"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 androidsends 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.aabto 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 submituses 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: trueexpects a dev server. Make sure testers get the production profile. - Missing environment variables. Values in a git-ignored
.envfile 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.
eas submit -p android --profile productionFind 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.
Run production mode locally
npx expo start --no-dev --minifyruns your JavaScript the way a release bundle does and shows errors that development mode never throws.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.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.
Make JavaScript errors readable
With Sentry, EAS Build uploads source maps automatically once
SENTRY_AUTH_TOKENis set as a sensitive EAS environment variable. Aftereas update, runnpx sentry-expo-upload-sourcemaps distso 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
.aaband 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.
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.
eas build -p android --profile productionFor 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.
eas update --channel production --message "Fix checkout crash" --environment production- Channels.
eas update:configuresets achannelon thepreviewandproductionbuild profiles. A build only receives updates published to its channel. From SDK 55,--environmentis required. - Runtime version. An update only reaches builds with the same runtime version. The
fingerprintpolicy 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.
- Expo: Submit to the Google Play Store
- Expo: App version management
- Expo: eas.json reference
- Expo: Managed credentials
- Android Developers: Target API level requirements
- Expo: EAS Update introduction
- Expo: Runtime versions and updates
- Expo: Debugging runtime issues
- Expo: Build APKs for Android emulators and devices
- Play Policy: Device and Network Abuse
- Play Console Help: Pre-launch reports
- Play Console Help: App testing requirements for new personal developer accounts
- Play Console Help: Set up an open, closed, or internal test
- Play Console Help: Deobfuscate or symbolicate crash stack traces
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