ReadyNative

Ship to Google Play

Build an Android App Bundle on EAS, create the app in Play Console, set up a service account, then submit to internal testing with one command.

Hey - by the end of this page your app is on the Play internal testing track, installable by the testers you invite, and every build goes up with one eas submit.

Before you start

  • Tier: Starter or Pro. The Free tier ships without eas.json - run bunx eas-cli build:configure once to create one, and skip the bun run doctor checks.
  • Time: 1-2 hours of your own work, plus 20-40 minutes per cloud build. A new Play developer account has to pass Google's identity verification first, which can take a few days.
  • Accounts: a Google Play developer account (a one-time fee), a free Expo account, and a Google Cloud project for the service account (step 5).
  • Previous tutorial: Ship to TestFlight. If you skipped it, do its steps 1-3 first: branding, bunx eas-cli init with the id pasted into app.easProjectId in readynative.config.ts, and your keys on EAS.

1. Set the package name for good

app.android.package in readynative.config.ts is your Play application id, for example com.yourcompany.trailmix. The production profile uses it as is (dev and preview append .dev / .preview). Once the first bundle is uploaded, Play locks the app to this id, so change it now or never. bun run doctor warns while it's still com.acme.app.

bun run doctor

You should see: no warning for app.android.package.

2. Check your keys are on EAS

.env is gitignored and never uploaded, so the build sees only the keys stored in its EAS environment - production for this profile. If you set them up for iOS, they're already there: Android uses the same environment. Check:

bunx eas-cli env:list --environment production

You should see: every key from your .env, with production values. Missing one? Add it with bunx eas-cli env:set --name <KEY> --value <value> --environment production --visibility plaintext (sensitive for keys without the EXPO_PUBLIC_ prefix) - see Environment variables → On EAS.

3. Build the production bundle

bunx eas-cli build --profile production --platform android

The production profile in eas.json sets APP_VARIANT=prod, builds an Android App Bundle (.aab) and auto-increments the version code. On the first Android build, EAS offers to generate an upload keystore; say yes. EAS keeps it, and bunx eas-cli credentials shows it and its SHA-256 fingerprint.

Using App Links? links.androidCertFingerprints needs the fingerprint of the key that signs what users install. New apps always use Play App Signing, so Google signs the installed app with its own app signing key: copy that key's SHA-256 from Play Console → Protected with Play → Play Store distribution → Play app signing and add it next to the upload key's.

You should see: the build marked Finished on expo.dev, with an .aab artifact.

4. Create the app in Play Console

In Play Console → Create app, pick the app name, the default language, App vs Game and Free vs Paid, and accept the declarations. Play doesn't ask for the package name here - it takes it from the first bundle that reaches Play.

You should see: the app's dashboard in Play Console, with a "Set up your app" task list.

5. Create a service account for eas submit

EAS submits with a Google service account key. Expo's service account guide has screenshots of every click; the short version:

  1. In Google Cloud Console, pick (or create) a project and create a service account. Copy its email, then Manage keys → Create new key → JSON and download the file.
  2. Enable the Google Play Android Developer API for that project.
  3. In Play Console → Users and permissions → Invite new users, invite the service account's email. On App permissions, pick your app and tick: view app information, edit and delete draft apps, release to production (with Play App Signing), release to testing tracks, manage testing tracks and tester lists, and manage store presence. It doesn't need admin or financial permissions.

Then either upload the key to EAS (expo.dev → your project → Credentials → Android → your application id → Add a Google Service Account Key, or bunx eas-cli credentials --platform android → Google Service Account), or keep it next to the project and point eas.json at it:

eas.json
"submit": {
  "production": {
    "android": {
      "serviceAccountKeyPath": "./google-service-account.json",
      "track": "internal",
      "releaseStatus": "draft"
    }
  }
}

The key can publish your app, so add google-service-account.json to .gitignore before you commit anything. With the key uploaded to EAS instead, leave serviceAccountKeyPath out.

track is internal (the default), alpha (closed testing), beta (open testing) or production. releaseStatus is completed (the default: rolled out), draft, inProgress (a staged rollout, with rollout between 0 and 1) or halted. While the app has never been published - Play Console still calls it a draft app - Google's API only accepts draft releases and rejects anything else with "Only releases with status draft may be created on draft app". Keep releaseStatus: "draft" until the app has gone through review, then drop it, or every later release also waits in draft for you to roll it out by hand.

You should see: the service account listed under Users and permissions, and the key under your project's Credentials on expo.dev (or the JSON file next to eas.json, ignored by git).

6. Upload the first bundle (optional)

eas submit can create the app's first release itself, so you can go straight to step 7. To do the first upload in the browser instead (Expo's manual guide walks through it):

  1. Test and release → Testing → Internal testing → Create new release.
  2. Accept Play App Signing when asked (Google holds the app signing key; your EAS keystore becomes the upload key).
  3. Upload the .aab from step 3 (download it from the build page on expo.dev), add release notes and roll it out.

Either way, under Internal testing → Testers, create an email list, add yourself, and open the opt-in link on your phone. After that the build installs from the Play Store app on every tester's device.

You should see: the opt-in page on your phone saying you're a tester.

7. Submit

bunx eas-cli build --profile production --platform android --auto-submit

Or submit a build you already have:

bunx eas-cli submit --profile production --platform android

The release lands on the internal testing track. With releaseStatus: "draft", open it in Play Console (Internal testing → Releases) and roll it out; otherwise testers get it within minutes. For later releases and wiring this into your own CI, see Ship to TestFlight → Automate it.

You should see: the release under Internal testing → Releases, and after rollout, the app installable from the Play Store app on your phone.

New personal accounts need a closed test first

Personal developer accounts created after November 13, 2023 can't publish to production until a closed test has run with at least 12 testers opted in continuously for at least 14 days; then you apply for production access on the Play Console dashboard. Internal testing doesn't count. Start the closed test early (track: "alpha" submits to it). The rule is for personal accounts; organisation accounts don't have it. Google's testing requirements page has the details.

Check it

bun run doctor

Every EAS row should be green. Before production, bun run doctor --store (Pro) checks what Play review looks at.

If it doesn't work

  • "Only releases with status draft may be created on draft app" - set "releaseStatus": "draft" in submit.production.android until the app has passed its first review.
  • eas submit says the service account has no permission - the invite in Play Console is missing an app permission from step 5, or the Google Play Android Developer API isn't enabled for its Cloud project. Permission changes can take a few minutes to apply.
  • "Package name … already exists" - another app on Play uses that id. Change app.android.package and build again.
  • The app works locally but shows "Configure …" from Play - a key is missing from the EAS production environment. See A key works locally but not in an EAS build.
  • The build fails on the keystore or credentials - see EAS build or credentials fail.

More in Troubleshooting.

Congrats 🎉

Your app is on Google Play internal testing, and the next build goes up with one command. Before you move to production, fill in the store listing, content rating and Data safety form in Play Console - Pass app review covers what reviewers check.

Next, ship a fix without a rebuild: Your first update.

On this page

Get ReadyNative