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
Inapps/mobile/app.json:
runtimeVersion: { "policy": "fingerprint" }andupdates.url(https://u.expo.dev/<projectId>).- Declare every capability the Apple bundle has enabled (for example
ios.usesAppleSignIn: true). ios.config.usesNonExemptEncryption: falseis already set and skips the export-compliance question.- Never change
ios.bundleIdentifierorandroid.packageafter the first store upload.
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:
eas.json, app config or the repo.
Ignore rules matter in a monorepo
EAS uploads every file git does not ignore. Ensurenode_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:createregisters each iPhone, theneas credentials:configure-build -p ios -e previewbuilds an ad-hoc profile that includes them. TestFlight builds need no device registration. - iOS submit:
--auto-submit, oreas 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.ipaand use Apple’s Transporter. - Android submit:
eas submit -p android --profile production --latestsends 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
stagingchannel, then republish the same update group toproduction: 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 tomain (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)
Underapps/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.