Maestro flow file structure for launchApp and assertions
A Maestro flow YAML file starts with an appId field containing the app's bundle identifier. Following the appId, a '---' separator begins the flow commands. Commands include: launchApp to launch the application, assertVisible to assert text is visible on screen, and tapOn to tap on UI elements. Example: appId: dev.expo.eastestsexample followed by --- and command list.
EAS Workflows E2E test workflow with Maestro job
An E2E test workflow YAML file under .eas/workflows defines a 'build' type job to compile the app and a 'maestro' type job that depends on the build output. The maestro job uses build_id from the build job's output and specifies flow_path as a list of .maestro flow file paths to execute. Example structure for Android:
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']
EAS Workflows directory structure
EAS Workflows are stored in a .eas/workflows directory at the root of the project. Workflow files are YAML files with descriptive names such as e2e-test-android.yml and e2e-test-ios.yml.
Maestro test flow with UI interaction and assertions
A Maestro flow can combine launch, interaction, and assertion commands. Example flow:
appId: dev.expo.eastestsexample
---
- launchApp
- tapOn: 'Explore.*'
- tapOn: '.*File-based routing'
- assertVisible: 'This app has two screens.*'
This flow launches the app, taps on the Explore tab, taps on the File-based routing section, and asserts that the text 'This app has two screens.' appears.
E2E test workflow for iOS platform
Create an iOS E2E test workflow similar to Android. The build job specifies platform: ios and profile: e2e-test. The maestro job receives the build_id from the iOS build and runs the same Maestro flows. Example:
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']
E2E test workflow automation with pull_request trigger
EAS Workflows can be triggered automatically by configuring the 'on' section with 'pull_request' trigger. Setting branches to ['*'] runs the workflow on every pull request to any branch. This allows E2E tests to run automatically when pull requests are opened without manual invocation.
Run E2E test workflow manually with EAS CLI
Execute an E2E test workflow manually using the command: eas workflow:run .eas/workflows/e2e-test-android.yml, replacing the filename with the appropriate workflow file path.
Basic preview update workflow YAML structure
A basic EAS workflow file for publishing preview updates on every commit is stored at .eas/workflows/publish-preview-update.yml. The workflow has the structure: name: 'Publish preview update', on: push with branches filter set to ['*'], jobs with a single job 'publish_preview_update' having name 'Publish preview update', type 'update', and params with branch set to '${{ github.ref_name || 'test' }}'.
Preview updates workflow for team review
Preview updates allow you to share changes with your team for review without requiring them to pull the latest changes and run them locally. You can access preview updates in the development build UI and through scannable QR codes on the EAS dashboard. This is particularly useful when publishing previews on every commit, enabling team members to review changes without pulling code locally.
Create production builds for app stores
Use eas workflow:create --template deploy to generate a release workflow that configures EAS Build and EAS Update, sets app identifiers, and writes the workflow to .eas/workflows/deploy.yml. This workflow fingerprints the project, builds and submits a production build when there are native changes, or publishes an over-the-air update when a matching build exists.
Install development builds to emulator and simulator
Use eas build:run -p android --latest to install the latest build on Android Emulator and eas build:run -p ios -e development-ios-simulator --latest to install on iOS Simulator. Then run npx expo start to start the development server.
Run an EAS workflow
Run eas workflow:run .eas/workflows/build.yml to execute a workflow. The workflow can be monitored on the project's workflows page in the Expo dashboard.
Create development build workflow with template
Run eas workflow:create --template build to generate a workflow file at .eas/workflows/build.yml that creates development builds for Android and iOS.
Workflow file location and generation
Workflow files are stored in the .eas/workflows directory with .yml extension. The eas workflow:create command with --template option generates workflow files in this directory and sets up the project for building or deployment.
Trigger workflows with GitHub pushes
Add an 'on' trigger to the workflow YAML file to trigger workflows on GitHub events. For example, to trigger on pushes to the main branch, add 'on: push: branches: [main]' at the top level of the workflow file.
Trigger workflows from App Store Connect events
Configure App Store Connect connection in EAS dashboard by navigating to Project settings > General > Connections and connecting the App Store Connect app. Then use on.app_store_connect in the workflow file to trigger on App Store Connect events such as ready_for_review or waiting_for_review app version states.
Link GitHub repository to EAS project
Navigate to the project's GitHub settings in the Expo dashboard, install the GitHub app, and select the GitHub repository that matches the Expo project to connect it.
EAS Workflows overview and first steps
EAS Workflows automate React Native CI/CD development and release processes. To get started, sign up for an Expo account, create a project with create-expo-app using template default@sdk-57, and install EAS CLI globally with npm install -g eas-cli.
EAS Workflows cloud machine types
Workflows run on EAS's managed infrastructure with the following machine types: Linux workers including linux-medium (4 vCPU, 16 GB RAM) and linux-large (8 vCPU, 32 GB RAM); Linux with nested virtualization for Android emulators including linux-medium-nested-virtualization and linux-large-nested-virtualization; and macOS workers for iOS builds and simulators including macos-medium (5 cores, 20 GB RAM) and macos-large (10 cores, 40 GB RAM).
Manual workflow execution without GitHub
Any workflow can be run manually using eas workflow:run regardless of the on trigger configuration, allowing workflows to be triggered without GitHub integration.
When to use EAS Workflows
EAS Workflows is recommended for automating builds, app store submissions, over-the-air updates, and web deployments; deploying web apps to EAS Hosting; running E2E tests with Maestro as part of CI; triggering builds and updates from GitHub push or pull request events; and achieving CI/CD without managing your own infrastructure or macOS machines. EAS Workflows is not recommended for highly customized pipelines that depend on non-EAS services like Docker or custom runners.
EAS Workflows current limitations
EAS Workflows currently has the following limitations: no shared workflow configurations (each workflow must be defined independently), and no matrix builds (cannot run multiple variations with different configurations in parallel).
EAS Workflows compatibility with React Native projects
EAS Workflows works with both CNG (Continuous Native Generation) projects and existing React Native projects, as long as the project is configured for EAS Build.
Custom jobs and scripts in workflows
Custom jobs with steps let you run shell commands, use built-in functions like eas/checkout and eas/install_node_modules, and set outputs for downstream jobs.
Parallel job execution in workflows
Jobs without dependencies run in parallel by default. Use the 'needs' keyword to specify that a job should wait for another job to succeed, or 'after' to wait for a job to complete regardless of success or failure.
EAS Workflows overview and purpose
EAS Workflows is a CI/CD service from EAS (Expo Application Services) that automates repeated tasks like building Android and iOS binaries, publishing over-the-air updates, submitting to app stores, running E2E tests, and deploying web apps to EAS Hosting. Workflows run in managed cloud environments with pre-packaged job types designed specifically for mobile app development.
EAS Workflows key features
EAS Workflows includes the following features: pre-packaged jobs that build, submit, and update apps, run Maestro E2E tests, and send Slack messages; flexible triggers on GitHub events, cron schedules, App Store Connect events, manual runs via eas workflow:run, or REST API; managed macOS and Linux workers requiring no infrastructure maintenance; a single dashboard on expo.dev showing builds, updates, test results, artifacts, and logs; and faster releases using fingerprint, get-build, and update jobs to skip redundant native builds and publish over-the-air updates.
Workflow file location and structure
Workflows are defined as YAML files in the .eas/workflows/ directory at the root of your project. Each file specifies a name, optional triggers (on), and one or more jobs that run in the cloud.
Create first workflow with EAS CLI
To create your first workflow, run the command 'eas workflow:create' and follow the prompts.
EAS Workflows triggers and execution
When an EAS project is linked to GitHub, teams can trigger workflows from GitHub events (push, pull request, labels) or schedules (cron), or run them manually via the EAS CLI. Workflows can also be triggered using the REST API or from App Store Connect events.
POST /v2/workflows/dispatch request body schema
The request body for POST /v2/workflows/dispatch has the following fields: appId (string UUID, required) - The EAS project ID found on the project page or in app config under extra.eas.projectId. gitRef (string, required) - A branch name (main), tag (v1.0.0), commit SHA, or fully qualified ref (refs/heads/main, refs/tags/v1.0.0). fileName (string, required) - The workflow file name only, such as deploy.yml; do not include the .eas/workflows/ prefix or any other path segments. inputs (object, optional) - Values for inputs declared under on.workflow_dispatch.inputs in the workflow file, validated against the declared schema.
Trigger workflow endpoint POST /v2/workflows/dispatch
POST /v2/workflows/dispatch resolves the workflow file on the given Git ref, validates inputs against the workflow_dispatch schema declared in the workflow, and enqueues a new run.
Robot users for production EAS integrations
For production integrations with the EAS REST API, create a robot user on the account that owns the project and give it a scoped role. The token then represents the robot user, not a person. You can revoke it without affecting any user. A personal access token also works for one-off scripts.
EAS REST API authentication header
Both EAS REST API endpoints require an EAS access token sent as a bearer token in the Authorization header. The header format is: Authorization: Bearer <EXPO_TOKEN>. Content-Type must be application/json.
EAS REST API base URL
All EAS REST API endpoints live under https://api.expo.dev. Requests and responses are JSON.
POST /v2/workflows/dispatch response
Returns 200 OK with a JSON response containing data.id (the new workflow run ID as UUID) and data.url (a link to the run in the dashboard).
GET /v2/workflows/runs/:workflowRunId error responses
GET /v2/workflows/runs/:workflowRunId returns: 400 if workflowRunId is not a valid UUID. 403 if the token does not have access to the run's project. 404 if no workflow run exists with that ID.
Poll workflow run until completion shell script example
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
GET /v2/workflows/runs/:workflowRunId response example
A successful response includes id (the workflow run ID), status (the run status), url (the dashboard link), gitCommitHash, gitCommitMessage, requestedGitRef, triggerEventType, createdAt, updatedAt, and jobs (array of job objects, each with id, key, name, type, status, requiredJobKeys, environment, outputs, errors, buildId, submissionId, createdAt, updatedAt).
Workflow job outputs and IDs
The outputs field in a job response contains any values the job sets with outputs: in the workflow file. Any referenced secrets appear as placeholders in the response. Jobs of type: build include buildId, and jobs of type: submission include submissionId. Each ID links to the underlying EAS Build or EAS Submit record.
Workflow job status values
Each job in a workflow run has a status, one of: new (the job is queued), in-progress (the job is running), action-required (the job is paused and waiting on a manual action such as approval), pending-cancel (a cancellation has been requested but the job has not stopped yet), success (the job succeeded; terminal state), failure (the job failed; terminal state), canceled (the job was canceled; terminal state), skipped (the job was skipped because a required upstream job did not succeed).
Get workflow run endpoint GET /v2/workflows/runs/:workflowRunId
GET /v2/workflows/runs/:workflowRunId returns the workflow run and the jobs it contains. Use this endpoint to poll for completion after triggering a run, or to render a run's details in your own UI.
POST /v2/workflows/dispatch error responses
POST /v2/workflows/dispatch returns: 400 if the request body fails schema validation or inputs do not match the workflow's declared schema. 403 if the token does not have access to the given appId. 404 if the Git ref does not exist in the linked repository or the workflow file is not found on that ref.
POST /v2/workflows/dispatch curl example
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" }
}'
EAS Workflows testflight job example
Example EAS Workflows configuration for TestFlight distribution in .eas/workflows/testflight.yml:
```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
```
This workflow builds an iOS app and then distributes it to TestFlight internal and external groups with changelog notes.
EAS Workflows testflight job for TestFlight automation
You can automate distributing a build to external groups with the pre-packaged 'testflight' job in EAS Workflows. It adds a build to internal and external groups, sets the 'What to Test' notes, and submits for Beta App Review. The 'submit_beta_review' parameter defaults to true when 'external_groups' is provided.
Update job type syntax and parameters
The update job type publishes an update using EAS Update. Its syntax is:
```yaml
jobs:
publish_update:
type: update
environment: production | preview | development # optional, defaults to production
env: # optional list of environment variables
ENV_VAR_NAME: value
params:
message: string # optional
platform: string # optional - android | ios | all, defaults to all
branch: string # optional
channel: string # optional - cannot be used with branch
rollout_percentage: number # optional - 0 to 100, defaults to 100
private_key_path: string # optional
upload_sentry_sourcemaps: boolean # optional - defaults to "try uploading, but don't fail the job if it fails"
hooks:
after_checkout: step[] # optional
before_install_node_modules: step[] # optional
after_install_node_modules: step[] # optional
before_update: step[] # optional
after_update: step[] # optional
```
Parameters:
- message (optional, string): Message to use for the update. If not provided, the commit message will be used.
- platform (optional, string): Platform to use for the update, either 'android', 'ios', or 'all'. Defaults to 'all'.
- branch (optional, string): Branch to use for the update. If not provided, the branch from the workflow run will be used.
- channel (optional, string): Channel to use for the update. Provide either a branch or a channel, not both.
- rollout_percentage (optional, number): Percentage of users this update should be immediately available to. Must be an integer between 0 and 100.
- private_key_path (optional, string): Path to the file containing the PEM-encoded private key for code signing.
- upload_sentry_sourcemaps (optional, boolean): Whether to upload Sentry sourcemaps. If not provided, the job checks if @sentry/react-native is installed and tries to upload if it is.
TestFlight job outputs
The TestFlight job produces the following outputs that can be referenced in subsequent jobs:
For build_id variant:
- apple_app_id (string): The Apple App ID of the submitted build.
- ios_bundle_identifier (string): The iOS bundle identifier of the submitted build.
For asc_build_id variant:
- asc_build_id (string): The App Store Connect build ID.
- apple_app_id (string): The Apple App ID of the submitted build.
- ios_bundle_identifier (string): The iOS bundle identifier of the submitted build.
TestFlight job type syntax for submitting an already uploaded build
The TestFlight job type with asc_build_id (submitting an already uploaded build) has the following syntax:
```yaml
jobs:
testflight_distribution:
type: testflight
params:
asc_build_id: string # required
internal_groups: string[] # optional
external_groups: string[] # optional
changelog: string # optional
submit_beta_review: boolean # optional
```
Parameters:
- asc_build_id (required, string): The ID of a build that already exists in App Store Connect.
- internal_groups (optional, string[]): An array of TestFlight internal group names to add the build to.
- external_groups (optional, string[]): An array of TestFlight external group names to add the build to.
- changelog (optional, string): Test notes ('What to Test') for TestFlight testers.
- submit_beta_review (optional, boolean): Whether to submit for Beta App Review.
TestFlight job type syntax for uploading a build
The TestFlight job type with build_id (uploading and submitting a build) has the following syntax:
```yaml
jobs:
testflight_distribution:
type: testflight
params:
build_id: string # required
profile: string # optional - default: production
internal_groups: string[] # optional
external_groups: string[] # optional
changelog: string # optional
submit_beta_review: boolean # optional
wait_processing_timeout_seconds: number # optional - default: 1800 (30 minutes)
hooks:
after_checkout: step[] # optional
before_install_node_modules: step[] # optional
after_install_node_modules: step[] # optional
```
Parameters:
- build_id (required, string): The ID of the iOS EAS Build to distribute.
- profile (optional, string): The submit profile to use. Defaults to 'production'.
- internal_groups (optional, string[]): An array of TestFlight internal group names to add the build to. Only include groups without automatic distribution enabled.
- external_groups (optional, string[]): An array of TestFlight external group names to add the build to.
- changelog (optional, string): Test notes ('What to Test') for TestFlight testers.
- submit_beta_review (optional, boolean): Whether to submit for Beta App Review. If not specified, defaults to true when external_groups are provided, false otherwise.
- wait_processing_timeout_seconds (optional, number): Timeout in seconds to wait for App Store Connect build processing. Defaults to 1800 (30 minutes). Only applies when distributing via groups or changelog.
Submit job outputs
The submit job produces the following outputs that can be referenced in subsequent jobs:
- apple_app_id (string): The Apple App ID of the submitted build.
- ios_bundle_identifier (string): The iOS bundle identifier of the submitted build.
- android_package_id (string): The Android package ID of the submitted build.
Submit job type syntax and parameters
The submit job type submits an Android or iOS build to the app store. Its syntax is:
```yaml
jobs:
submit_to_store:
type: submit
params:
build_id: string # required
profile: string # optional - default: production
groups: string[] # optional
hooks:
after_checkout: step[] # optional
before_install_node_modules: step[] # optional
after_install_node_modules: step[] # optional
before_submit: step[] # optional
after_submit: step[] # optional
```
Parameters:
- build_id (required, string): The ID of the build to submit.
- profile (optional, string): The submit profile to use. Defaults to 'production'.
- groups (optional, string[]): TestFlight internal group names to add the build to. For more TestFlight distribution options, see the TestFlight job.
Get-build job outputs
The get-build job produces the following outputs that can be referenced in subsequent jobs:
- build_id (string): The ID of the retrieved build.
- app_build_version (string): The build version of the app.
- app_identifier (string): The bundle identifier/package name of the app.
- app_version (string): The version of the app.
- channel (string): The update channel used for the build.
- distribution (string): The distribution method used.
- fingerprint_hash (string): The fingerprint hash of the build.
- git_commit_hash (string): The git commit hash used for the build.
- platform (string): The platform the build was created for.
- profile (string): The build profile used.
- runtime_version (string): The runtime version used.
- sdk_version (string): The SDK version used.
- simulator (string): Whether the build is for simulator.
Get-build job type
The get-build job type retrieves an existing build from EAS that matches the provided parameters. Its syntax is:
```yaml
jobs:
get_build:
type: get-build
params:
platform: ios | android # optional
profile: string # optional
distribution: store | internal | simulator # optional
channel: string # optional
app_identifier: string # optional
app_build_version: string # optional
app_version: string # optional
git_commit_hash: string # optional
fingerprint_hash: string # optional
sdk_version: string # optional
runtime_version: string # optional
simulator: boolean # optional
wait_for_in_progress: boolean # optional
```
Parameters:
- platform (optional, string): The platform to get the build for, either 'ios' or 'android'.
- profile (optional, string): The build profile to use.
- distribution (optional, string): The distribution method, either 'store', 'internal', or 'simulator'.
- channel (optional, string): The update channel.
- app_identifier (optional, string): The bundle identifier/package name.
- app_build_version (optional, string): The build version.
- app_version (optional, string): The app version.
- git_commit_hash (optional, string): The git commit hash.
- fingerprint_hash (optional, string): The fingerprint hash.
- sdk_version (optional, string): The SDK version.
- runtime_version (optional, string): The runtime version.
- simulator (optional, boolean): Whether to get a simulator build.
- wait_for_in_progress (optional, boolean): Whether to wait for a matching in-progress build. Default: false.
Fingerprint job outputs
The fingerprint job produces the following outputs that can be referenced in subsequent jobs:
- android_fingerprint_hash (string): The fingerprint hash for Android.
- ios_fingerprint_hash (string): The fingerprint hash for iOS.
Fingerprint job type for CNG workflows
The fingerprint job type calculates a fingerprint of your project and has the following syntax:
```yaml
jobs:
fingerprint:
type: fingerprint
environment: production | preview | development # optional, defaults to production
env: # optional list of environment variables
ENV_VAR_NAME: value
hooks:
after_checkout: step[] # optional
before_install_node_modules: step[] # optional
after_install_node_modules: step[] # optional
```
Note: This job type only supports CNG (Continuous Native Generation) workflows. If you commit your android or ios directories, the fingerprint job won't work.
Deploy job outputs
The deploy job produces the following outputs that can be referenced in subsequent jobs:
- deploy_json (string): JSON object containing the deployment details (output of npx eas-cli deploy --json).
- deploy_url (string): URL to the deployment. It uses production URL if this was a production deployment. Otherwise, it uses the first alias URL or the deployment URL.
- deploy_alias_url (string): Alias URL to the deployment (for example, https://account-project--alias.expo.app).
- deploy_deployment_url (string): Unique URL to the deployment (for example, https://account-project--uniqueid.expo.app).
- deploy_identifier (string): Identifier of the deployment.
- deploy_dashboard_url (string): URL to the deployment dashboard (for example, https://expo.dev/projects/[project]/hosting/deployments).
Deploy job type for EAS Hosting
The deploy job type has the following syntax:
```yaml
jobs:
deploy_web:
type: deploy
params:
alias: string # optional
prod: boolean # optional
source_maps: boolean # optional
hooks:
after_checkout: step[] # optional
before_install_node_modules: step[] # optional
after_install_node_modules: step[] # optional
```
Parameters:
- alias (optional, string): The alias to deploy to.
- prod (optional, boolean): Whether to deploy to production.
- source_maps (optional, boolean): Whether to upload source maps with the deployment.
Build job type syntax and parameters
The build job type has the following syntax:
```yaml
jobs:
build_app:
type: build
runs_on: string # optional
params:
platform: android | ios # required
profile: string # optional - default: production
message: string # optional
refresh_ad_hoc_provisioning_profile: boolean # optional
hooks:
before_install_node_modules: step[] # optional
after_install_node_modules: step[] # optional
```
Parameters:
- platform (required, string): The platform to build for, either 'android' or 'ios'.
- profile (optional, string): The build profile to use. Defaults to 'production'.
- message (optional, string): Custom message attached to the build, corresponds to the --message flag when running eas build.
- refresh_ad_hoc_provisioning_profile (optional, boolean): Refreshes the managed ad hoc provisioning profile before an iOS internal build starts, corresponds to the --refresh-ad-hoc-provisioning-profile flag when running eas build.
Doc job parameters
The doc job type accepts the following parameter: md (string, required) - Markdown content to display; supports ${{ ... }} workflow interpolation.