Skip to main content
Status: target. apps/mobile has eas.json with development, preview and production profiles, but there is no Expo project link (extra.eas.projectId), no store records, no .eas/workflows/, no updates config and no staging profile. Licensed packages (HeroUI Pro, Uniwind Pro) are not installed, so the licence-token steps below apply only once they are. App: Expo SDK 57, code-based React Navigation.

Concepts

Native change means a new build. JS-only change means an update. EAS owns build, signing, submit and updates. We do not build or sign on a laptop for release. Always run EAS from apps/mobile, never the repo root. Every build costs money: ask before each one, and never loop builds.

One-time store bootstrap (human only)

No API can do these steps. A human does them; an agent never does, and no value goes into chat, a ticket or a commit. Tokens go to EAS as secret variables (Secrets).
1

Apple: the company's organisation account

The team must be the company’s organisation account, never an individual enrolment or another party’s team: every Apple artifact belongs to the team, and moving an app later is painful. The Account Holder accepts agreements, sets banking and requests App Store Connect API access once.
2

Apple: create the app record

App Store Connect, Apps, New App. The bundle ID must equal ios.bundleIdentifier in apps/mobile/app.json. Note the numeric Apple ID and put it in eas.json under submit.production.ios.ascAppId. Never delete an app record to fix a mistake: the bundle ID and often the name become permanently unusable. The bundle can be switched only until the first build is uploaded.
3

Apple: API key for submit

The Account Holder or an Admin creates a Team Key (Users and Access, Integrations) with role App Manager, never Admin. The .p8 downloads once. Give it to the EAS credentials step only, then delete the local copy. App Managers cannot create keys, and “Generate” in the EAS prompt fails for them with a 403.
4

Google Play: create the app and the first upload

Create the app in Play Console and upload the first .aab manually to an internal track. Google requires this before any API upload.
5

Google Play: service account

In Google Cloud create a service account with Play Console release-manager access and download its JSON key once. Upload it through eas credentials -p android. Do not commit it or store it anywhere else.
6

Expo: link the project

cd apps/mobile, eas login, eas init. This writes extra.eas.projectId into the app config. Connect the repository in the Expo project settings with base directory apps/mobile.
7

Generate credentials

Run EXPO_NO_CAPABILITY_SYNC=1 eas credentials -p ios and set up the distribution certificate and provisioning profile. Apple allows only two APNs push keys per team and one key serves every app: at the limit, reuse an existing .p8 by uploading it, and revoke a key only when no live app uses it. Android: eas credentials -p android generates an upload keystore, which EAS stores.

Configuration to add before the first store build

In apps/mobile/app.json:
  • runtimeVersion: { "policy": "fingerprint" } and updates.url (https://u.expo.dev/<projectId>).
  • Declare every capability the Apple bundle has enabled (for example ios.usesAppleSignIn: true).
  • ios.config.usesNonExemptEncryption: false is already set and skips the export-compliance question.
  • Never change ios.bundleIdentifier or android.package after the first store upload.
In apps/mobile/eas.json, add environment and channel per profile, and a staging profile that extends base with autoIncrement: true. appVersionSource: remote already keeps build numbers on EAS, and autoIncrement bumps the build number; you bump version yourself for each store release. Check what a profile resolves to with eas config --profile <name> --platform ios. EXPO_PUBLIC_* variables are inlined into the JS bundle and are public. Create them as plain text. A licence token or private key is a secret variable, which only the build servers see:
Never put a secret value in eas.json, app config or the repo.

Ignore rules matter in a monorepo

EAS uploads every file git does not ignore. Ensure node_modules/, ios/, android/, DerivedData, agent worktrees (.claude/worktrees/) and test output are ignored. An .easignore file replaces .gitignore for uploads entirely, so copy the rules into it if you add one. If a build hangs at “Compressing project files”, find the culprit with git status --short | grep '^??', du -sh <path> and git check-ignore -v <path>.

Build, submit, update

  • Internal testers: eas device:create registers each iPhone, then eas credentials:configure-build -p ios -e preview builds an ad-hoc profile that includes them. TestFlight builds need no device registration.
  • iOS submit: --auto-submit, or eas submit -p ios --profile production --latest. Apple processes the upload for 10 to 30 minutes, then add testers under TestFlight, Internal Testing. External testers need one Beta App Review. Fallback: download the .ipa and use Apple’s Transporter.
  • Android submit: eas submit -p android --profile production --latest sends a draft to the internal track; promote it in Play Console.
  • An update reaches only builds with the same runtime version. Any native change gives a new runtime version, so build first. Test on the staging channel, then republish the same update group to production: promote, do not rebuild.
  • Store rules: an update may fix bugs and change JS or UI, but must not change the app’s core purpose. Big features go through review.

Release checklists

JS-only change: merge to main (workflow publishes to staging); test on a staging build; eas update:republish to production. Native change (new native dependency, SDK upgrade, app config, permissions): merge (workflow builds staging and sends it to TestFlight); testers verify; bump version for a store release; run the production workflow; store review, then release. Before the first public release: production API URL and keys only in the EAS production environment; store listing complete (category, age rating, privacy questionnaire, screenshots per language, support and privacy-policy URLs, a review demo account); the app sits under your own company’s Apple and Google accounts.

EAS Workflows (target)

Under apps/mobile/.eas/workflows/: Validate with eas workflow:validate .eas/workflows/<file>.yml; run by hand with eas workflow:run. Automatic TestFlight submit needs the App Store Connect API key from the bootstrap; the Apple ID plus app-specific password route is interactive only.

Troubleshooting (each one happened)

Checks that back this page

See also Secrets, CI and Upgrade.