Build and submit workflow with job dependencies
A submit workflow can depend on a build job by using the needs field. The submit job references the build_id output from the build job using the syntax: needs: [build_ios] and build_id: ${{ needs.build_ios.outputs.build_id }}
iOS production build workflow structure
An iOS production build workflow file contains: name field (iOS production build), on section with push trigger for main branch, jobs section with build_ios job, job type: build, and params with platform: ios and profile: production.
Running workflows with eas workflow:run command
A workflow can be triggered by running the EAS CLI command 'eas workflow:run' followed by the path to the workflow file, for example: eas workflow:run .eas/workflows/build-ios-production.yml
EAS Workflows capabilities
EAS Workflows can build, submit, and update your app, while also running other jobs such as Maestro tests, unit tests, and custom scripts.
Workflow file location and naming
Workflow files are stored in the .eas/workflows/ directory at the root of the project. For example, a build workflow would be named .eas/workflows/build-ios-production.yml and a submit workflow would be named .eas/workflows/submit-ios.yml.
github.sha context in ref_delete runs
When using ref_delete triggers, github.sha refers to the default branch, not the deleted ref's last commit. Use github.ref_name to get the name of the deleted branch.
ref_delete trigger for branch cleanup
The ref_delete event runs when a GitHub branch matching the patterns is deleted. In a branch cleanup workflow, use on.ref_delete with a branches pattern to trigger deletion of EAS Update branches when their corresponding Git branches are deleted.
Branch cleanup workflow example
This workflow demonstrates cleaning up EAS Update branches when GitHub branches are deleted:
```yaml
name: Branch cleanup
on:
ref_delete:
branches: ['*', '!main']
jobs:
delete-update-branch:
type: branch-delete
params:
branch_name: ${{ github.ref_name }}
```
This workflow triggers on deletion of all branches except main, and deletes the EAS Update branch with the same name as the deleted Git branch.
Create development builds workflow YAML
The following workflow creates development builds for Android and iOS (both device and simulator) that run in parallel:
name: Create development builds
jobs:
android_development_build:
name: Build Android
type: build
params:
platform: android
profile: development
ios_device_development_build:
name: Build iOS device
type: build
params:
platform: ios
profile: development
ios_simulator_development_build:
name: Build iOS simulator
type: build
params:
platform: ios
profile: development-simulator
Development builds support multiple platforms and device types
Development builds can be created for each platform and for both physical devices, Android emulators, and iOS simulators. Users can access these builds with the eas build:dev command.
PostHog integration workflow example
EAS Workflows includes an example showing how to send events, roll out feature flags, and gate rollouts on metrics with PostHog.
Deploy to production workflow example
EAS Workflows includes an example showing how to build and submit to the app stores or send an over-the-air update when merging to main.
Clean up update branches workflow example
EAS Workflows includes an example showing how to delete EAS Update branches when GitHub branches are deleted.
Run E2E tests workflow example
EAS Workflows includes an example showing how to run E2E tests.
Create development builds workflow example
EAS Workflows includes an example showing how to kick off development builds in parallel for each platform.
EAS Workflows examples overview
EAS Workflows provides examples of common React Native CI/CD workflows for developing, reviewing, and releasing apps. These examples help teams automate development, code review, and continuous release processes.
Publish preview update workflow example
This EAS workflow publishes a preview update for every commit on every branch. The workflow file is placed at .eas/workflows/publish-preview-update.yml and contains: name: Publish preview update, on: push with branches: ['*'], jobs with job name publish_preview_update, type: update, and params with branch: ${{ github.ref_name || 'test' }}. This allows teams to review changes on every commit without pulling code locally.
Add push trigger to workflow on main branch
Add the `on` trigger to a workflow file to trigger it when commits are pushed. Example: add `on: push: branches: ['main']` to trigger the workflow when commits are pushed to the main branch.
Run production builds workflow
After configuring credentials, run the production builds workflow with `eas workflow:run .eas/workflows/create-production-builds.yml`.
Configure App Store Connect connection in EAS dashboard
To use App Store Connect triggers in workflows, configure the connection by opening the EAS dashboard, selecting the project, navigating to Project settings > General > Connections, and connecting the App Store Connect app.
Production build workflow with build jobs for each platform
To create production builds without deployment, use a workflow with a `build` job for each platform. Each job should have `type: build`, `params.platform` (android or ios), and `params.profile` (production). This workflow uses the `production` profile from `eas.json`. Example: `build_android` and `build_ios` jobs with their respective platform and profile parameters.
Use slack job type in workflows
Workflows can include `slack` job type to send notifications. The slack job requires `type: slack`, `environment` parameter, and `params` with `webhook_url` (which can reference environment variables like `${{ env.SLACK_WEBHOOK_URL }}`) and `message`.
Use app_store_connect trigger in workflows
Trigger workflows from App Store Connect events using `on.app_store_connect`. Example: `on: app_store_connect: app_version: states: [ready_for_review, waiting_for_review]` triggers the workflow when an app version is ready for review or waiting for review.
Create first development builds workflow with eas workflow:create
Run the command `eas workflow:create --template build` to create a workflow that builds development builds for Android and iOS. Follow the prompts and `[Action requested]` steps when they appear. The command generates a workflow file at `.eas/workflows/build.yml` and sets up the project for building on EAS.
Prerequisites for EAS Workflows
To get started with EAS Workflows, you need: (1) An Expo account (sign up at https://expo.dev/signup), (2) A project created with `npx create-expo-app@latest --template default@sdk-57`, `yarn create expo-app --template default@sdk-57`, `pnpm create expo-app --template default@sdk-57`, or `bun create expo --template default@sdk-57`, (3) EAS CLI installed with `npm install -g eas-cli`.
GitLab CI EAS Build configuration example
Add the following code snippet in .gitlab-ci.yml at the root of your project repository:
image: node:alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm
# or with Yarn:
#- .yarn
stages:
- build
before_script:
- npm ci --cache .npm
# or with Yarn:
#- yarn install --cache-folder .yarn
eas-build:
stage: build
script:
- apk add --no-cache bash
- npx eas-cli build --platform all --non-interactive --no-wait
Travis CI EAS Build configuration example
Add the following code snippet in .travis.yml at the root of your project repository:
language: node_js
node_js:
- node
- lts/*
cache:
directories:
- ~/.npm
before_script:
- npm install -g npm@latest
jobs:
include:
- stage: build
node_js: lts/*
script:
- npm ci
- npx eas-cli build --platform all --non-interactive --no-wait
Authenticate with Expo on CI using EXPO_TOKEN
To authenticate as the owner of the app on CI, store a personal access token in the EXPO_TOKEN environment variable in the CI settings. The personal access token can be created by following the personal access tokens documentation.
Pre-packaged jobs available in EAS Workflows
EAS Workflows include ready-to-use pre-packaged jobs that can build, submit, and update your app, run Maestro E2E tests, send Slack messages, and more. Custom jobs can also run any command you need.
EAS Workflows trigger options
Workflows can be triggered by GitHub events (push, pull request, label, branch or tag deletion), on a cron schedule, on App Store Connect events, manually with 'eas workflow:run', or from the REST API.
Trigger workflow curl example
The following curl command triggers a workflow:
```bash
curl -X POST "https://api.expo.dev/v2/workflows/dispatch" \
-H "Authorization: Bearer $EXPO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"appId": "a415eac6-231a-4b38-b481-3255a59f13b8",
"gitRef": "main",
"fileName": "deploy.yml",
"inputs": { "environment": "production" }
}'
```
This example shows how to dispatch a workflow with inputs passed as JSON.
Workflows REST API base URL and authentication
The EAS Workflows REST API endpoints are located at https://api.expo.dev. All endpoints require an EAS access token sent as a bearer token in the Authorization header in the format: Authorization: Bearer <EXPO_TOKEN>. Requests and responses are JSON. For production integrations, use a robot user token; for one-off scripts, a personal access token works.
POST /v2/workflows/dispatch endpoint response
The POST /v2/workflows/dispatch endpoint returns 200 OK with a JSON response containing data.id (the new workflow run ID as a UUID) and data.url (a link to the run in the dashboard at https://expo.dev/accounts/{accountName}/projects/{projectName}/workflows/{runId}).
Workflow run response structure
The GET /v2/workflows/runs/:workflowRunId endpoint returns a JSON response with data object containing: id (workflow run UUID); status (one of new, in-progress, action-required, success, failure, canceled); url (dashboard link); gitCommitHash (commit SHA); gitCommitMessage (commit message); requestedGitRef (the Git ref that was requested); triggerEventType (event type like MANUAL); createdAt (ISO 8601 timestamp); updatedAt (ISO 8601 timestamp); jobs (array of job objects). Jobs of type build include buildId, and jobs of type submission include submissionId. The outputs field contains any values the job sets with outputs: in the workflow file, with secrets appearing as placeholders.
Trigger workflow and poll until terminal status example
The following bash script triggers a workflow run and polls until it reaches a terminal status:
```bash
RUN_ID=$(curl -s -X POST "https://api.expo.dev/v2/workflows/dispatch" \
-H "Authorization: Bearer $EXPO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"appId":"...","gitRef":"main","fileName":"deploy.yml"}' \
| jq -r '.data.id')
while true; do
STATUS=$(curl -s "https://api.expo.dev/v2/workflows/runs/$RUN_ID" \
-H "Authorization: Bearer $EXPO_TOKEN" \
| jq -r '.data.status')
echo "status: $STATUS"
case "$STATUS" in
success|failure|canceled) break ;;
esac
sleep 10
done
```
This example shows how to capture the run ID from a dispatch response and repeatedly check the run status until it reaches success, failure, or canceled.
EAS Workflows iOS submission workflow file
Create a workflow file at .eas/workflows/submit-ios.yml with the following configuration to build and submit iOS apps automatically:
```yaml
name: Submit iOS
on:
push:
branches: ['main']
jobs:
build_ios:
name: Build iOS app
type: build
params:
platform: ios
profile: production
submit_ios:
name: Submit to TestFlight
needs: [build_ios]
type: testflight
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
```
This workflow builds an iOS app on every push to the main branch and submits it to TestFlight. Trigger the workflow manually with 'eas workflow:run submit-ios.yml'.
EAS Workflows testflight job example configuration
The testflight job in EAS Workflows is configured with parameters: build_id (output from a build job), internal_groups (array of internal group names), external_groups (array of external group names), and changelog (what to test notes). Example:
```yaml
jobs:
build_ios:
name: Build iOS
type: build
params:
platform: ios
profile: production
testflight:
name: Distribute to TestFlight
type: testflight
needs: [build_ios]
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
internal_groups: ['QA Team']
external_groups: ['Public Beta']
changelog: |
What's new in this release:
- New features
- Bug fixes
```
Production mode minification and performance
Production mode minifies your code and better represents the performance your app will have on end users' devices.
Development mode default behavior with npx expo start
By default, running a project locally with npx expo start runs it in development mode.
Production mode for published projects and standalone apps
A published project with eas update or any standalone app will run in production mode.
Development mode performance cost
Your app runs slower in development mode. Any time you are testing your app's performance, make sure to disable development mode.
Example Android E2E test workflow
Example workflow file for running E2E tests on Android:
```yaml
name: e2e-test-android
on:
pull_request:
branches: ['*']
jobs:
build_android_for_e2e:
type: build
params:
platform: android
profile: e2e-test
maestro_test:
needs: [build_android_for_e2e]
type: maestro
params:
build_id: ${{ needs.build_android_for_e2e.outputs.build_id }}
flow_path: ['.maestro/home.yml', '.maestro/expand_test.yml']
```
This workflow builds an .apk for Android using the e2e-test build profile, then runs the specified Maestro flows on the built APK. The pull_request trigger runs the workflow automatically when a pull request is opened.
Example iOS E2E test workflow
Example workflow file for running E2E tests on iOS:
```yaml
name: e2e-test-ios
on:
pull_request:
branches: ['*']
jobs:
build_ios_for_e2e:
type: build
params:
platform: ios
profile: e2e-test
maestro_test:
needs: [build_ios_for_e2e]
type: maestro
params:
build_id: ${{ needs.build_ios_for_e2e.outputs.build_id }}
flow_path: ['.maestro/home.yml', '.maestro/expand_test.yml']
```
This workflow builds an .app for iOS using the e2e-test build profile, then runs the specified Maestro flows on the built app.
Maestro flows directory structure
Create a directory called .maestro in the root of your project directory at the same level as eas.json. This directory contains the flows you configure for E2E tests.
Example Maestro home.yml flow
A Maestro flow file that launches an app and asserts text visibility. Example content:
```yaml
appId: dev.expo.eastestsexample
---
- launchApp
- assertVisible: 'Welcome!'
```
Replace the appId with your actual app id.
Maestro test job dependencies in workflows
In EAS Workflows, use the needs field to specify job dependencies. For example, needs: [build_android_for_e2e] makes the maestro_test job wait for build_android_for_e2e to complete. Access output from the previous job using ${{ needs.build_android_for_e2e.outputs.build_id }}.
Example Maestro expand_test.yml flow
A Maestro flow file that navigates through app screens and interacts with UI elements. Example content:
```yaml
appId: dev.expo.eastestsexample
---
- launchApp
- tapOn: 'Explore.*'
- tapOn: '.*File-based routing'
- assertVisible: 'This app has two screens.*'
```
Replace the appId with your actual app id. This flow launches the app, taps the Explore button, taps the File-based routing collapsible, and asserts visibility of specific text.
Maestro job type in EAS Workflows
Use type: maestro in EAS Workflows to run Maestro E2E tests. Required parameters are build_id (the build to test) and flow_path (an array of Maestro flow file paths to execute).
E2E test workflow directory structure
Create an .eas/workflows directory at the root of your project. Add YAML files for your E2E test workflows in this directory, such as e2e-test-android.yml or e2e-test-ios.yml.
Pull request trigger for E2E workflows
E2E test workflows can be configured to run automatically when a pull request is opened. Use the pull_request trigger with branches specified in the on section of the workflow YAML file. Example: on: pull_request: branches: ['*'] runs the workflow on every pull request.
EAS Workflows fingerprint job type
The fingerprint job type takes a hash of the native characteristics of the project using Expo Fingerprint and produces separate outputs for android_fingerprint_hash and ios_fingerprint_hash.
EAS Workflows get-build job type
The get-build job type checks if a build already exists for a specific fingerprint hash. It takes params for fingerprint_hash and profile, and outputs build_id if a matching build exists.
Deploy to production workflow example
The following YAML workflow runs on each push to the main branch, performs fingerprinting, checks for existing builds for both Android and iOS, builds if needed, submits builds to stores if built, or publishes over-the-air updates if builds already exist:
```yaml
name: Deploy to production
on:
push:
branches: ['main']
jobs:
fingerprint:
name: Fingerprint
type: fingerprint
environment: production
get_android_build:
name: Check for existing android build
needs: [fingerprint]
type: get-build
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
profile: production
get_ios_build:
name: Check for existing ios build
needs: [fingerprint]
type: get-build
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }}
profile: production
build_android:
name: Build Android
needs: [get_android_build]
if: ${{ !needs.get_android_build.outputs.build_id }}
type: build
params:
platform: android
profile: production
build_ios:
name: Build iOS
needs: [get_ios_build]
if: ${{ !needs.get_ios_build.outputs.build_id }}
type: build
params:
platform: ios
profile: production
submit_android_build:
name: Submit Android Build
needs: [build_android]
type: submit
params:
build_id: ${{ needs.build_android.outputs.build_id }}
submit_ios_build:
name: Submit iOS Build
needs: [build_ios]
type: submit
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
publish_android_update:
name: Publish Android update
needs: [get_android_build]
if: ${{ needs.get_android_build.outputs.build_id }}
type: update
params:
branch: production
platform: android
publish_ios_update:
name: Publish iOS update
needs: [get_ios_build]
if: ${{ needs.get_ios_build.outputs.build_id }}
type: update
params:
branch: production
platform: ios
```
EAS Workflows build job type
The build job type creates a new build for the specified platform. It takes params for platform (android or ios) and profile, and outputs build_id.
EAS Workflows update job type
The update job type publishes an over-the-air update. It takes params for branch (the update channel), and platform (android or ios).
Deploy to production workflow overview
The deploy to production workflow runs on each push to the main branch. It takes a hash of the native characteristics of the project using Expo Fingerprint, checks if a build already exists for that fingerprint, and either builds and submits to app stores if no build exists, or sends an over-the-air update if a build already exists.
EAS Workflows submit job type
The submit job type submits a build to the app stores. It takes a param for build_id to identify which build to submit.
after context in EAS Workflows
The after context is a record of upstream jobs listed in the current job's after field, whether or not they succeeded. Each entry provides the same status and outputs shape as the needs context.
needs context in EAS Workflows
The needs context is a record of upstream jobs listed in the current job's needs field. Each entry provides the job's status (success, failure, or skipped) and its outputs.
steps context in EAS Workflows
The steps context is a record of the steps in the current job, keyed by step id. Each entry exposes the outputs set with the set-output function. This context is only available within a job's steps.