Building and releasing with EAS and Fastlane
Configure app.json and signing, build and submit with EAS, ship over-the-air updates safely, and run the release pipeline for both stores.
Configuration and signing
{
"expo": {
"name": "Field Notes",
"slug": "field-notes",
"version": "2.4.0",
"runtimeVersion": { "policy": "appVersion" },
"updates": { "url": "https://u.expo.dev/PROJECT_ID" },
"ios": {
"bundleIdentifier": "com.example.fieldnotes",
"buildNumber": "1",
"infoPlist": { "NSCameraUsageDescription": "Scan equipment labels." }
},
"android": {
"package": "com.example.fieldnotes",
"versionCode": 42,
"permissions": ["CAMERA", "POST_NOTIFICATIONS"]
},
"extra": { "eas": { "projectId": "PROJECT_ID" } }
}
}eas build:configure
eas credentials # let EAS manage or upload your own keys
eas build --platform ios --profile production
eas build --platform android --profile production
eas submit --platform ios --latest
eas submit --platform android --latest --track internalruntimeVersiondecides which binaries an over-the-air update may target. Changing native code without bumping it can ship JavaScript that calls a module the installed binary does not have.- Credentials stored by EAS are encrypted; if you manage your own, keep the keystore in a secret manager and never commit it.
- Increment
versionCodeon every Android upload — Play rejects a duplicate, and the failure appears late in the process.
Over-the-air updates
# publish a JS-only fix to the production channel
eas update --branch production --message "Fix currency rounding"
# roll back to the previous update on that branch
eas update:rollback --branch production --message "Reverting bad release"| Change | Needs a store release? | OTA allowed? |
|---|---|---|
| Copy and styling | No | Yes |
| Business logic in JavaScript | No | Yes |
| New native module | Yes | No |
| Changed permission in the manifest | Yes | No |
| New app icon | Yes | No |
Publish updates in stages. A rollout to a small channel first, then to everyone, means a bad JavaScript change reaches a fraction of users and is reverted with one command.
A release pipeline
# .github/workflows/release.yml
name: release
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: yarn }
- run: yarn install --frozen-lockfile
- run: yarn lint && yarn test --ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: eas build --platform all --profile production --non-interactive
- run: eas submit --platform all --latest --non-interactive⚠️
An over-the-air update can reach every user within minutes, which is powerful and dangerous. Gate it behind the same review and test process as a store build, keep the previous update addressable for rollback, and never let a CI job publish to production without a deliberate trigger.
FAQ
EAS Build or a local build with Fastlane?
EAS removes the need to maintain Mac hardware and signing configuration, and integrates with updates. A local Fastlane setup is more predictable for a team that already has Mac runners and wants full control of the build environment.
How do I test an over-the-air update before publishing?
Publish to an internal distribution channel that points at the same binary, have the team install that build, and verify. Only then promote the update to the production channel.
Related
Native modules, TurboModules and the New Architecture Testing React Native apps
Last refreshed 2026-09-18.