EAS Submit iOS: publish an Expo app to the App Store
By Mehdi Azizi
Published , 8 min
EAS Submit uploads your iOS build to App Store Connect, where it lands in TestFlight. You still submit it for review yourself in App Store Connect.
eas submit --platform ios uploads your iOS build to App Store Connect, where it shows up in TestFlight once Apple has processed it. The command doesn't submit your app to Apple's review: you still pick the build and submit it for review in App Store Connect. To automate it, it needs your app record's number (ascAppId) and an App Store Connect API key.
I publish my iOS apps, including Solys, TradaZen and Nolera, with Expo, EAS Build and EAS Submit. This guide follows Expo's documentation on iOS submission, reviewed on October 25, 2026. If you don't have a Mac, the full walkthrough is in publish an iOS app without a Mac. Here, we focus on the upload.
What you need before the first command
Expo lists three prerequisites:
- A paid Apple Developer account. Price and sign-up are covered in Apple developer account.
- A bundle identifier in
ios.bundleIdentifier. According to the Expo config reference, you make it up, but it has to be unique on the App Store. - EAS CLI installed, then
eas loginwith your Expo account.
You also need a signed production build. EAS Build creates it with the certificate and profile it manages for you, as explained in iOS certificate and provisioning profile.
The App Store Connect record: you or EAS?
EAS Submit uploads a binary to an app that has to exist in App Store Connect.
You can create it yourself. Apple's help describes the New App window: platform, name, primary language, bundle ID, SKU and user access. You need the Account Holder, Admin or App Manager role.
EAS can also create it on the first upload. According to Expo's TestFlight page, eas submit creates the record when you upload your first build. Per the eas.json reference, setting ascAppId skips this step. The name comes from appName, or if that's missing, from the name in your config. The primary language comes from language, which defaults to en-US. If your app launches in French first, set language before that first upload.
Then copy the record's number into ascAppId. Expo tells you where to find it: open your app in App Store Connect, then App Information. It's the Apple ID field, under General Information. It's a number, not your bundle identifier.
Why an API key instead of your Apple ID
EAS Submit connects to Apple with your Apple ID or with an App Store Connect API key.
The Apple ID runs into two-factor authentication, which Apple requires for every App Store Connect user: someone has to confirm the sign-in. An app-specific password, passed in EXPO_APPLE_APP_SPECIFIC_PASSWORD, skips that step, but Expo considers it less secure than the API key, its default and recommended method. The key lets you authenticate with zero interaction, so you can automate the upload.
Two ways to get one:
- Let EAS manage it. Run
eas credentials --platform ios, pick theproductionprofile, sign in to your Apple account, then choose "App Store Connect: Manage your API Key". - Bring your own key. In App Store Connect: Users and Access, Integrations tab, App Store Connect API, Team Keys, then Generate API Key. You need an Admin account, and Expo's guide gives the key the Admin role.
In the second case, write down the Key ID, shown in the list of active keys, and the Issuer ID, at the top of the Integrations page. Above all, keep the .p8 file: you can only download it once, and Apple doesn't keep a copy.
According to Apple, an Admin key can create and delete users on your account. Apple says never to store the key in a code repository.
The minimal eas.json to copy
All values are fake: replace them with yours.
{
"cli": {
"appVersionSource": "remote"
},
"build": {
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"ascAppId": "1234567890",
"appleTeamId": "ABCDE12345",
"ascApiKeyPath": "./keys/AuthKey_XXXXXXXXXX.p8",
"ascApiKeyIssuerId": "your-issuer-id",
"ascApiKeyId": "XXXXXXXXXX"
}
}
}
}
appVersionSourceandautoIncrement: EAS servers store the build number and bump it with every production build. Expo recommends this remote mode since EAS CLI 12.ascAppId: your app record's number. Without it, non-interactive mode fails.appleTeamId: your Apple Developer team ID.ascApiKeyPath,ascApiKeyIssuerId,ascApiKeyId: your API key. All three fields accept environment variables. Add thekeysfolder to your.gitignore.
The appleId field only matters if you sign in with your Apple ID. If EAS already manages your key, also remove the three ascApiKey fields. On my three apps, checked on October 25, the production submit profile contains only ascAppId, plus appleTeamId on Solys: no key in the file.
Run the build, then the upload
eas build --platform ios --profile production
eas submit --platform ios
With no other option, eas submit asks you to pick the build to upload and, on the first run, asks for your Apple ID. The production submit profile is used by default if it exists. The EAS CLI reference lists the options that skip the picker:
| Option | What it does |
|---|---|
--latest | Uploads the latest iOS build |
--id | Uploads a specific EAS build |
--path | Uploads a local .ipa file |
--url | Uploads an online archive |
--groups | Adds to an internal group |
--what-to-test | Fills in "What to Test" |
To chain everything, add --auto-submit to the build command. According to Expo's page on automatic submissions, the finished build goes to EAS Submit with the submit profile that has the same name as the build profile. To pick another one: --auto-submit-with-profile=name. The build goes to TestFlight, not to review.
In continuous integration, add --non-interactive and an EXPO_TOKEN token. The command then can't ask you anything: it needs ascAppId in the profile and an API key, either the one EAS attached to the app or yours in the three ascApiKey fields.
What happens after the upload
1. Apple processes the build. It has to go through processing before it shows up in App Store Connect, and Apple sends you an email when it's done. Expo usually mentions 10 to 15 minutes before you see it in TestFlight.
2. Export compliance. The Missing Compliance status means your encryption declaration is missing. App Store Connect asks these questions for every new version, unless the Info.plist already answers them. With Expo, the key is ios.config.usesNonExemptEncryption:
{
"expo": {
"ios": {
"config": {
"usesNonExemptEncryption": false
}
}
}
}
According to Apple, false is fine if your app, libraries included, uses no encryption or only exempt encryption. System encryption, like HTTPS through URLSession, usually is. Only declare false if it's true for your app: you're accountable for the declaration. Like any Info.plist key, it requires a new build.
3. TestFlight. At the Ready to Submit status, according to Apple, the build can go to internal testers, go through beta review for external ones, or go to App Store review. The groups field in the profile adds it to your internal groups. Everything else is in TestFlight to test your app.
Uploading isn't submitting for review
Expo says it plainly: a TestFlight build isn't published to the App Store automatically. EAS Submit doesn't fill in your listing or your screenshots. You still have four steps in App Store Connect, described in Apple's help on submitting for review:
- Fill in the required metadata for the version.
- Pick the build in the version's Build section.
- Click "Add for Review". The version moves to Ready for Review, but nothing has gone to review yet.
- Click "Submit for Review". The status changes to In Review when the review starts.
Common errors and how to fix them
| Error shown | Cause | Fix |
|---|---|---|
| ITMS-90189 | Build number already used | autoIncrement |
| ITMS-90062 or 90186 | Version already closed | Bump version |
| "Set ascAppId" | ascAppId missing in CI | Add ascAppId |
| "API Keys cannot" | No key attached | eas credentials |
| ITMS-90683 | Missing purpose string | ios.infoPlist |
| ITMS-91053 | Incomplete manifest | ios.privacyManifests |
| ITMS-90725 | iOS SDK too old | Upgrade the Expo SDK |
| Missing Compliance | Encryption not declared | usesNonExemptEncryption |
According to Expo, a build number that's already used is a common cause of upload rejection: it's all in build number and version. Three errors have their own guide: ITMS-90683 with Expo, ITMS-91053 with Expo and ITMS-90725.
The two quoted messages come from EAS and get fixed in its config. Missing Compliance can also be fixed by answering the questions in App Store Connect. For every other row, the fix touches the binary: rebuild, then upload again. An over-the-air update doesn't change the Info.plist, the manifest or the numbers.
Ship your first app without fumbling
The command is just one step. The iOS Kit starts from the Starter Code of my apps, subscriptions included, and walks you through four steps all the way to the App Store, at your own pace.
If you're preparing your first launch, take the free diagnostic: it tells you what's really holding you back and sends you my Apple anti-rejection checklist. For the listing, screenshots and approval, the page publish an app on the App Store covers every step.
Frequently asked questions
How do I publish an Expo app to the App Store?
Run a production build with EAS Build, then upload it with eas submit --platform ios. After Apple processes it, it shows up in TestFlight. Then in App Store Connect, fill in the listing, pick that build in the version, and click "Add for Review" and "Submit for Review".
Does EAS Submit send my app to Apple's review?
No. EAS Submit uploads the binary to App Store Connect, and the build lands in TestFlight. Submitting for review is done manually in App Store Connect, once the listing is filled in and the build is picked.
Where do I find my app's ascAppId?
In App Store Connect, open your app, then App Information. It's the Apple ID field, under General Information. It's a number, not to be confused with your bundle identifier.
Do I need an Apple ID to use eas submit?
On the first run, the command asks for it. After that, with ascAppId in the profile and an App Store Connect API key, it can run without interaction, even in continuous integration. The API key is Expo's default and recommended method.
How long before I see the build in TestFlight?
Expo usually mentions 10 to 15 minutes of processing on Apple's side, and Apple emails you when it's done. If the build stays at Missing Compliance, answer the encryption questions, then declare it in your config for future builds.