Shipnative
ShipnativeShipnative
Sign in
ReferenceAugust 2026 · 8 min read

eas submit: Every Flag, Credential, and Error

eas submit is a small command with an unusually large surface area, because everything it touches belongs to Apple or Google. The command itself almost never fails; the credentials and the binary do. This is the reference version — what each flag does, which credential to use and why, how to make it run unattended in CI, and a decoder for the errors that come back as five-digit ITMS codes.

What the command actually does

eas submit is a delivery mechanism, nothing more. It takes a finished .ipa or .aab, authenticates against the store, and uploads it. It does not compile, it does not fill in your store listing, and — the part that surprises people — it does not submit anything for review. A successful eas submitmeans “the binary is now sitting in App Store Connect.” You still open the console and press the buttons.

# The 90% case: build, then send the newest one
eas build --platform ios --profile production
eas submit --platform ios --latest

# Or chain them
eas build --platform all --profile production --auto-submit

# Send a specific older build
eas submit --platform android --id 8f3c1e42-...

# Send a binary you built elsewhere
eas submit --platform android --path ./app-release.aab

The flags

FlagWhat it doesWhen it matters
--platform ios | android | allWhich store to submit toRequired in --non-interactive
--latestSubmit the most recent finished buildThe everyday flag
--id <build-id>Submit one specific EAS buildUse when re-submitting an older build
--path <file>Submit a local .ipa / .aabFor binaries not built on EAS
--url <url>Submit a remotely hosted binaryRare; useful for external CI
--profile <name>Pick a submit profile from eas.jsonDefaults to "production"
--non-interactiveNever prompt; fail insteadMandatory in CI
--wait / --no-waitBlock until the store finishes processingWaiting surfaces errors in the same log
--verboseFull transport outputFirst thing to add when a submit fails opaquely

--waitis the underrated one. Without it the command exits as soon as the upload finishes, and Apple’s processing errors arrive by email twenty minutes later instead of in your terminal. In CI, waiting is what makes a red build actually red.

iOS credentials: use the API key

You can authenticate with your Apple ID, and for a first manual submission that’s fine — EAS will prompt and store it. But an Apple ID needs two-factor, which means it can never work unattended. An App Store Connect API key can, and it’s revocable without touching your account password. Generate one in App Store Connect → Users and Access → Integrations → App Store Connect API, role App Manager. The .p8 file downloads exactly once.

// eas.json
{
  "build": {
    "production": { "distribution": "store", "autoIncrement": true }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "you@example.com",
        "ascAppId": "6478123456",          // numeric App Store Connect app ID
        "appleTeamId": "AB12CD34EF",
        "ascApiKeyPath": "./secrets/AuthKey_ABC123.p8",
        "ascApiKeyIssuerId": "69a6de70-...",
        "ascApiKeyId": "ABC123"
      },
      "android": {
        "serviceAccountKeyPath": "./secrets/play-service-account.json",
        "track": "internal",               // internal | alpha | beta | production
        "releaseStatus": "draft"
      }
    }
  }
}

Do not commit those two secret files. Add secrets/ to .gitignore and in CI supply them as base64 environment variables written to disk at the start of the job, or reference EXPO_ASC_API_KEY_PATH and GOOGLE_SERVICE_ACCOUNT_KEY_PATH. A leaked .p8 is publish access to your App Store account.

Android: the first release must be manual

This one costs everybody an evening. Google Play refuses API uploads for a package name that has never had a release. So the sequence for a brand-new Android app is:

  1. eas build --platform android --profile production, then download the AAB.
  2. Create the app in Play Console and upload that AAB by hand to the internal testing track. Roll it out.
  3. Create a service account (Google Cloud → IAM), grant it access in Play Console under Users and permissions with at least Release to testing tracks, and download the JSON.
  4. Every release after that: eas submit --platform android --latest.

The permission grant propagates slowly — a 403 immediately after granting access often resolves itself in ten minutes. Retry once before you go re-reading IAM docs.

Running it unattended

# .github/workflows/release.yml (excerpt)
- run: npm ci
- run: npx eas-cli@latest build --platform all --profile production \
         --non-interactive --no-wait
- run: npx eas-cli@latest submit --platform all --profile production \
         --latest --non-interactive --wait
  env:
    EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}

Three rules for CI: authenticate with EXPO_TOKEN (a robot access token from expo.dev, not your password), always pass --non-interactive so a missing credential fails loudly instead of hanging on a prompt, and set autoIncrement: true in the build profile so you never lose a run to a duplicate build number.

Error decoder

ErrorReal causeFix
ITMS-90283 / invalid provisioning profileBuilt with a dev or ad-hoc profileRebuild with distribution: "store"
ITMS-90189 / redundant binary uploadThat version + build number already existsBump buildNumber, or use autoIncrement in eas.json
ITMS-91053 missing API declarationA dependency touches a required-reason APIAdd PrivacyInfo.xcprivacy entries, then rebuild
Google: "APK/AAB not found"No manual first release for this package nameUpload the first AAB by hand in Play Console
Google: 403 from the service accountMissing release permission or unlinked API accessGrant "Release to testing tracks" and re-link in Play Console
Apple: "two-factor required"Apple ID auth used in a non-interactive runSwitch to an App Store Connect API key

The pattern across almost all of them: a failed submit is rarely fixable by resubmitting. If the binary is wrong — wrong signature, duplicate build number, missing privacy manifest — you need a new build, not another upload. Change the config, rebuild, then submit.

Getting to the point where this is your only problem

Everything above assumes you have an Expo app worth submitting. ShipNative generates that part — a real React Native app from a description, previewable on your phone, exported as a standard Expo project with an eas.jsonalready in it. The submission commands on this page are the same ones you’ll run. Before you get here, walk the submission checklist — most failed submits are caught there.

Frequently Asked Questions

What does eas submit do?

It uploads a finished binary — an .ipa or .aab — to App Store Connect or Google Play from Expo's servers. It handles the transport and credentials only. It does not build the app, submit it for review, or release it to users; after eas submit succeeds the build appears in TestFlight or your Play internal track and you still press the buttons.

What is the difference between eas build and eas submit?

eas build compiles your project into an installable binary. eas submit takes an existing binary and delivers it to the store. They are separate steps so you can rebuild without resubmitting and resubmit without rebuilding — though eas build --auto-submit chains them in one command.

Do I need a Mac to run eas submit?

No. Both the build and the upload run on Expo's macOS workers, so a Windows or Linux machine works for the whole pipeline. You do need a paid Apple Developer account ($99/year) and, for Google Play, the $25 one-time registration.

How do I run eas submit in CI without prompts?

Pass --non-interactive together with an explicit --platform and either --latest or --id, and put the credentials in eas.json under submit profiles. Use an App Store Connect API key (via EXPO_ASC_API_KEY_PATH or ascApiKeyPath) rather than an Apple ID, because Apple ID sign-in requires two-factor and cannot complete unattended.

Why does eas submit say "Invalid provisioning profile" or ITMS-90283?

The binary was signed with a development or ad-hoc profile instead of an App Store distribution profile. Rebuild with a production profile — in eas.json set distribution to "store" on that build profile — since resubmitting the same artifact will always fail the same way.

Why does the Google Play submission fail the first time?

Google Play requires that the very first release of a package name be uploaded manually through the Play Console web UI. eas submit can only take over once the app exists and has one release on some track. The other frequent cause is a service account without the "Release to testing tracks" permission.

→

Expo EAS App Store Submission Checklist

The 12 things to verify before you submit at all.

See checklist →
→

TestFlight for AI-Generated Apps

What happens after the upload lands.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.