eas submit is one of the best things in the Expo toolchain. One command takes a production build and uploads it to App Store Connect or Google Play, from macOS, Linux, or Windows. No Xcode, no Transporter, no Play Console upload dialog.
It's also the source of a very common misunderstanding: "I ran eas submit, so my app is submitted." Not quite. EAS Submit uploads your build. Getting it in front of users still involves a handful of steps that live in the store consoles, and those are where first launches stall.
This post covers both halves: how to set up EAS Submit properly for iOS and Android (including a recent change most older tutorials get wrong), and a clear map of what it doesn't do, so you can plan for it.
What EAS Submit does
-
iOS: uploads your
.ipato App Store Connect. The build appears in TestFlight after processing, usually 10 to 15 minutes. You don't need a Mac. -
Android: uploads your
.aabto a Google Play track (internal by default) and can roll it out, leave it as a draft, or publish to production, depending on your submit profile. -
Automation: runs the same way locally, in EAS Workflows, or in any CI with an
EXPO_TOKEN.
Setting up iOS
1. Bundle identifier and a production build
{
"expo": {
"ios": { "bundleIdentifier": "com.yourcompany.yourapp" }
}
}
eas build --platform ios --profile production
2. A submit profile with your App Store Connect app ID
Create the app record in App Store Connect first. Then add its ID to eas.json:
{
"submit": {
"production": {
"ios": {
"ascAppId": "1234567890"
}
}
}
}
You'll find the ID in App Store Connect → your app → App Information → Apple ID (a number, not your email).
3. An App Store Connect API key (for anything automated)
Interactive eas submit can prompt for your Apple ID. CI and workflows can't, so set up an API key once:
eas credentials --platform ios
# production → log in → App Store Connect: Manage your API Key
# → Set up your project to use an API Key for EAS Submit
If you prefer to manage the key yourself, set ascApiKeyPath, ascApiKeyIssuerId, and ascApiKeyId in the submit profile instead.
4. Submit
eas submit --platform ios --profile production
Setting up Android
1. Package name and an .aab
{
"expo": {
"android": { "package": "com.yourcompany.yourapp" }
}
}
eas build --platform android --profile production
The default production profile produces an Android App Bundle (.aab), which Google Play requires. An .apk profile is useful for installing on devices but can't be submitted.
2. Create the app in Play Console
EAS can't create the app record. Click Create app in Play Console and choose the package name.
3. A Google Service Account key
EAS needs a service account key to upload on your behalf. Create one (Expo links a step-by-step guide from its docs), then upload it:
eas credentials --platform android
# production → Google Service Account → Upload a Google Service Account Key
4. A submit profile
{
"submit": {
"production": {
"android": {
"track": "internal",
"releaseStatus": "draft"
}
}
}
}
-
trackpicks where the build lands:internal,alpha(closed testing),beta(open testing), orproduction. -
releaseStatus: "draft"uploads without rolling out, so you can review and release in Play Console.
5. Submit
eas submit --platform android --profile production
The change older tutorials get wrong
For years, every Expo guide said the same thing: you must upload your first Android build manually in Play Console before EAS Submit will work. Expo's current docs say otherwise. On a first submission, the default eas submit works out of the box and creates the app's first release on the internal testing track, as long as the app exists in Play Console and EAS has your service account key.
The app stays in draft status in Play Console until you finish the store listing and setup tasks. Which brings us to the other half.
Build and submit in one step
eas build --platform all --profile production --auto-submit
--auto-submit hands each finished build to EAS Submit using the submit profile with the same name.
Automate it with EAS Workflows
Workflows are YAML files in .eas/workflows/. This one builds both platforms on every push to main, sends iOS to TestFlight, and uploads Android to your configured track:
# .eas/workflows/release.yml
name: Release
on:
push:
branches: ['main']
jobs:
build_ios:
name: Build iOS
type: build
params:
platform: ios
profile: production
build_android:
name: Build Android
type: build
params:
platform: android
profile: production
testflight:
name: Send to TestFlight
needs: [build_ios]
type: testflight
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
submit_android:
name: Submit to Google Play
needs: [build_android]
type: submit
params:
profile: production
build_id: ${{ needs.build_android.outputs.build_id }}
Run it manually the first time:
eas workflow:run release.yml
The testflight job can also share the build with internal and external tester groups. If you'd rather use GitHub Actions or another CI, run eas submit there with an EXPO_TOKEN set.
What EAS Submit doesn't do
Here's the part that surprises people. After a successful eas submit, these steps still stand between your build and the public:
On iOS
| Step | Where |
|---|---|
| Submit the build for App Review | App Store Connect |
| Store listing: name, subtitle, description, keywords, screenshots | App Store Connect |
| App Privacy details (the "nutrition label") | App Store Connect |
| Age rating questionnaire | App Store Connect |
| Demo account for the reviewer, if login is required | App Review Information |
| Beta App Review for external TestFlight testers (first build) | Automatic, but it's a queue |
| Responding to rejections | Resolution Center |
EAS gets the binary into TestFlight. Sending it to App Review is still a step you take in App Store Connect.
On Android
| Step | Where |
|---|---|
| Store listing, screenshots, feature graphic | Play Console |
| Data Safety form | Play Console |
| Content rating questionnaire | Play Console |
| Target audience, ads declaration, app access | Play Console |
| Account deletion web link (if users can sign up) | Data Safety form |
| 12 testers for 14 days in closed testing (new personal accounts) | Play Console + your testers |
| Applying for production access | Play Console |
| Promoting a release to production | Play Console, or track: "production" once allowed |
The closed-testing requirement is the long one: new personal developer accounts must keep at least 12 testers opted in for 14 continuous days before production opens up. EAS can upload to the closed-testing track (track: "alpha") on every build, but the 14 days are calendar days.
A release pipeline that covers both halves
Put together, a realistic first launch looks like this:
- Day 1: create both app records; set up the ASC API key and Google service account key; add submit profiles.
-
Day 1: first
eas build --auto-submit. iOS lands in TestFlight; Android lands on the internal track. -
Days 1–3: recruit Android testers; switch the Android profile to
track: "alpha"so every build goes to closed testing. - Days 2–5: fill in listings, screenshots, privacy details, Data Safety, ratings, and the demo account.
- Day 5 onward: submit iOS for App Review from App Store Connect.
- Day 17 onward: apply for Google Play production access, then promote.
After launch, the pipeline is fully automatic: every merge to main builds, uploads, and lands in TestFlight and your Play track, and you promote from there.
Common EAS Submit errors
-
"No suitable application records were found" (iOS): the bundle ID doesn't match an app in App Store Connect, or
ascAppIdis wrong. -
Version code or build number already used: set
"cli": { "appVersionSource": "remote" }and"autoIncrement": trueon the production build profile so EAS increments for you. - Service account permission errors (Android): the service account needs access to the app in Play Console's Users and permissions.
- CI can't authenticate with Apple: interactive Apple ID login doesn't work in CI. Use the App Store Connect API key.
The short version
EAS Submit removes the upload step from your release. It doesn't remove the store consoles. Set up the API key and service account once, automate builds and uploads with a workflow, and put the listing, privacy, testing, and review steps on your calendar from day one.
If you'd rather hand off the console half (listings, privacy forms, Data Safety, testers, and the review back-and-forth), LetsDeployIt handles App Store and Google Play submission end to end.
What's the step that surprised you most on your first release?
Top comments (1)
For HelperBook, my Android app on Google Play, the surprise wasn't the upload, it was R8. The release build shrinks code the debug build never touches, so I had to write keep rules for SQLDelight and kotlinx-serialization, or the database layer and the export format would have broken only in the build users get. The second was the Data Safety form: simple when all the data stays on the phone, but Crashlytics still has to be declared, so I kept names and amounts out of its logs to keep the answers true. Worth installing a signed release build on a real device before the first upload.