EAS Workflows
A checklist for YAML pipelines that build, test, submit, and update Expo SDK 57 apps on EAS infrastructure. Work through every item before calling your release train production-ready.
Search across all documentation pages
A checklist for YAML pipelines that build, test, submit, and update Expo SDK 57 apps on EAS infrastructure. Work through every item before calling your release train production-ready.
workflow:run; Tier 3–5 before store automation goes live.pr-quality.yml, preview-pr.yml, store-release.yml - not one mega-file.eas build does not mean workflow YAML is wired correctly.| Tier | Items | When | Owner |
|---|---|---|---|
| 1 | 1–6 | First workflow | Mobile engineer |
| 2 | 7–12 | PR + preview automation | Mobile + QA |
| 3 | 13–18 | Store release train | EM + mobile lead |
| 4 | 19–24 | OTA + rollback readiness | Mobile + backend |
| 5 | 25–30 | Operations + cost control | Platform / EM |
EAS project linked - app.json / app.config.ts contains extra.eas.projectId after eas init.
eas workflow:run prompts to create a project interactively.npx eas-cli@latest init on SDK 57; commit the generated project ID.eas.json profiles exist for each intent - at minimum preview, production, and e2e-test.
params.profile references a missing key..eas/workflows/ directory committed - YAML is source code, reviewed like application changes.
quality-gates.yml first; expand from there.EXPO_TOKEN robot account in CI secrets - not a personal access token tied to one developer.
Workflow CLI tested locally - npx eas-cli@latest workflow:run .eas/workflows/quality-gates.yml.
on: pull_request.Concurrency group configured - cancel superseded runs on rapid PR pushes.
concurrency:
cancel_in_progress: true
group: ${{ workflow.filename }}-${{ github.ref }}type: build - lint, typecheck, test on eas/checkout.
# .eas/workflows/quality-gates.yml
name: Quality Gates
on:
pull_request:
branches: [main]
jobs:
quality:
steps:
- uses: eas/checkout
- uses: eas/use_npm_token
- uses: eas/install_node_modules
- run: npx expo customize tsconfig.json
- run: npm run format:check
- run: npm run lint
- run: npm run typecheck
- run: npm run test -- --cineeds: [quality] on downstream build jobs - builds wait for green scripts.
build_ios and quality run in parallel.needs array on every type: build job.PR trigger matches branch policy - pull_request.branches: [main] or release/*.
Preview build profile for PR workflows - distribution: internal or simulator / apk as needed.
production credentials.preview profile in eas.json - Internal Distribution.Build job outputs build_id consumed downstream - Maestro and submit reference the same artifact.
--latest instead of pinned ID. build_android:
needs: [quality]
type: build
params:
platform: android
profile: preview
maestro_smoke:
needs: [build_android]
type: maestro
params:
build_id: ${{ needs.build_android.outputs.build_id }}
flow_path: [".maestro/smoke.yml"]- **Priority:** P0 before automated submit.
12. Maestro appId matches eas.json bundle IDs - flows launch the built app, not Expo Go.
- Signal: Maestro job succeeds locally but fails in workflow - wrong appId.
- Fix: Maestro E2E.
- Priority: P0 on first E2E workflow.
Production build workflow triggered by semver tag - on.push.tags: ["v*"].
main submits to stores.workflow_dispatch for hotfixes.type: build with profile: production and autoIncrement - build numbers advance without hand-editing plists.
versionCode / build number."autoIncrement": true in eas.json production profile.type: submit references build_id from paired build job - not --latest.
submit_ios:
needs: [smoke_ios]
type: submit
params:
platform: ios
build_id: ${{ needs.build_ios.outputs.build_id }}- **Priority:** P0 - [Automated Store Promotion](./automated-store-promotion.md).
16. Maestro smoke gates submit - at least login + one revenue-critical path.
- Signal: Submit runs immediately after build with no device verification.
- Fix: needs: [maestro_smoke] on submit jobs.
- Priority: P0 for automated promotion.
iOS and Android submit jobs parallel after platform-specific smoke - do not serialize unnecessarily.
Slack / email notification on workflow failure - on-call knows before users do.
type: update job targets explicit channel - production, staging, not implicit default.
OTA workflow runs quality gates first - same lint/tsc/test as PR checks.
needs: [quality] before type: update.runtimeVersion policy documented - OTA compatible only with matching store binaries.
OTA message includes Git SHA - traceability in EAS dashboard.
message: ${{ github.sha }} in update params.Rollback workflow documented and tested - eas update --roll-back or channel republish.
Native-impacting changes blocked from OTA-only workflow - config plugin edits trigger build workflow.
expo-camera not in installed binary.native-change.Skip tokens documented - [eas skip] in commit message for docs-only pushes.
PR preview builds label-gated or path-filtered - not every typo triggers EAS build.
Build profile matrix documented - which profile runs on PR, nightly, release.
Secrets rotated on schedule - EXPO_TOKEN, App Store Connect API key, Play service account.
Workflow duration monitored - quality + build + Maestro p95 tracked.
install_node_modules caching.Disaster drill: failed Maestro blocks submit - verified in staging, not assumed.
needs wiring; smoke failure ignored.type | Purpose | Key params |
|---|---|---|
| (custom steps) | lint, tsc, test, doctor | run: npm run … |
build | Cloud native binary | platform, profile |
maestro | E2E on built artifact | build_id, flow_path |
submit | Store upload | platform, build_id |
update | OTA bundle | channel, message |
# .eas/workflows/store-release.yml
name: Store Release
on:
push:
tags: ["v*"]
jobs:
quality:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- run: npm run lint && npm run typecheck && npm run test -- --ci
build_android:
needs: [quality]
type: build
params: { platform: android, profile: production }
smoke_android:
needs: [build_android]
type: maestro
params:
build_id: ${{ needs.build_android.outputs.build_id }}
flow_path: [".maestro/smoke.yml"]
submit_android:
needs: [smoke_android]
type: submit
params:
platform: android
build_id: ${{ needs.build_android.outputs.build_id }}
update_production:
needs: [submit_android]
type: update
params:
channel: production
message: "Post-release OTA sync ${{ github.sha }}".eas/workflows/*.yml at the project root (sibling to eas.json). Name by intent: quality-gates.yml, preview-pr.yml, store-release.yml.
npx eas-cli@latest workflow:run .eas/workflows/quality-gates.ymlUse workflow_dispatch in on: for GitHub UI triggers.
GitHub Actions excels at PR checks on every push. EAS Workflows excel at build → Maestro → submit chains on Expo infra. Many teams use both - keep npm scripts identical.
Yes - chain type: build then type: update with needs. OTA-only hotfixes can use a separate workflow with just quality + update jobs.
e2e-test profileStack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026