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 internal
  • runtimeVersion decides 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 versionCode on 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"
ChangeNeeds a store release?OTA allowed?
Copy and stylingNoYes
Business logic in JavaScriptNoYes
New native moduleYesNo
Changed permission in the manifestYesNo
New app iconYesNo

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.

Native modules, TurboModules and the New Architecture Testing React Native apps

Last refreshed 2026-09-18.