Syntax for EAS Workflows
Edit this page
Reference guide for the EAS Workflows configuration file syntax.
A workflow is a configurable automated process made up of one or more jobs. You must create a YAML file to define your workflow configuration.
To get started with workflows, see Get Started with EAS Workflows or see Examples for complete workflow configurations.
Workflow files
Workflow files use YAML syntax and must have either a .yml
or .yaml
file extension. If you're new to YAML and want to learn more, see Learn YAML in Y minutes.
Workflow files are located in the .eas/workflows directory in your project. The .eas directory should be at the same level as your eas.json file.
For example:
my-app
.eas
workflows
create-development-builds.yml
publish-preview-update.yml
deploy-to-production.yml
eas.json
Configuration Reference
Below is a reference for the syntax of the workflow configuration file.
name
The human-friendly name of the workflow. This is displayed on the Expo dashboard on the workflows list page and is the title of the workflow's detail page.
name: My workflow
on
The on
key defines which GitHub events trigger the workflow. Any workflow can be triggered with the eas workflow:run
command, regardless of the on
key.
on:
# Trigger on pushes to main branch
push:
branches:
- main
# And on pull requests starting with 'version-'
pull_request:
branches:
- version-*
on.push
Runs your workflow when you push a commit to matching branches and/or tags.
With the branches
list, you can trigger the workflow only when those specified branches are pushed to. For example, if you use branches: ['main']
, only pushes to the main
branch will trigger the workflow. Supports globs. By using the !
prefix you can specify branches to ignore (you still need to provide at least one branch pattern without it).
With the tags
list, you can trigger the workflow only when those specified tags are pushed. For example, if you use tags: ['v1']
, only the v1
tag being pushed will trigger the workflow. Supports globs. By using the !
prefix you can specify tags to ignore (you still need to provide at least one tag pattern without it).
When neither branches
nor tags
are provided, branches
defaults to ['*']
and tags
defaults to []
, which means the workflow will trigger on push events to all branches and will not trigger on tag pushes. If only one of the two lists is provided the other defaults to []
.
on:
push:
branches:
- main
- feature/**
- !feature/test-** # other branch names and globs
tags:
- v1
- v2*
- !v2-preview** # other tag names and globs
on.pull_request
Runs your workflow when you create or update a pull request that targets one of the matching branches.
With the branches
list, you can trigger the workflow only when those specified branches are the target of the pull request. For example, if you use branches: ['main']
, only pull requests to merge into the main branch will trigger the workflow. Supports globs. Defaults to ['*']
when not provided, which means the workflow will trigger on pull request events to all branches. By using the !
prefix you can specify branches to ignore (you still need to provide at least one branch pattern without it).
With the types
list, you can trigger the workflow only on the specified pull request event types. For example, if you use types: ['opened']
, only the pull_request.opened
event (sent when a pull request is first opened) will trigger the workflow. Defaults to ['opened', 'reopened', 'synchronize']
when not provided. Supported event types:
opened
reopened
synchronize
labeled
on:
pull_request:
branches:
- main
- feature/**
- !feature/test-** # other branch names and globs
types:
- opened
# other event types
on.pull_request_labeled
Runs your workflow when a pull request is labeled with a matching label.
With the labels
list, you can specify which labels, when assigned to your pull request, will trigger the workflow. For example, if you use labels: ['Test']
, only labeling a pull request with the Test
label will trigger the workflow. Defaults to []
when not provided, which means no labels will trigger the workflow.
You can also provide a list of matching labels directly to on.pull_request_labeled
for simpler syntax.
on:
pull_request_labeled:
labels:
- Test
- Preview
# other labels
Alternatively:
on:
pull_request_labeled:
- Test
- Preview
# other labels
jobs
A workflow run is made up of one or more jobs.
jobs:
job_1:
# ...
job_2:
# ...
jobs.<job_id>
Each job must have an ID. The ID should be unique within the workflow and can contain alphanumeric characters and underscores. For example, my_job
in the following YAML:
jobs:
my_job:
# ...
jobs.<job_id>.name
The human-friendly name of the job displayed on the workflow's detail page.
jobs:
my_job:
name: Build app
jobs.<job_id>.environment
Sets the EAS environment variable environment for the job. There are three possible values:
production
(default)preview
development
The environment
key is available on all jobs except for the pre-packaged build
, submit
, and get-build
jobs.
jobs:
my_job:
environment: production | preview | development
jobs.<job_id>.defaults.run.working_directory
Sets the directory to run commands in for all steps in the job.
jobs:
my_job:
defaults:
run:
working_directory: ./my-app
steps:
- name: My first step
run: pwd # prints: /home/expo/workingdir/build/my-app
defaults
Parameters to use as defaults for all jobs defined in the workflow configuration.
defaults.run.working_directory
Default working directory to run the scripts in. Relative paths like "./assets" or "assets" are resolved from the app's base directory.
defaults.tools
Specific versions of tools that should be used for jobs defined in this workflow configuration. Follow each tool's documentation for available values.
Tool | Description |
---|---|
node | Version of Node.js installed via nvm . |
yarn | Version of Yarn installed via npm -g . |
pnpm | Version of pnpm installed via npm -g . |
bun | Version of Bun installed by passing bun-v$VERSION to Bun install script. |
ndk | Version of Android NDK installed through sdkmanager . |
bundler | Version of Bundler that will be passed to gem install -v . |
fastlane | Version of fastlane that will be passed to gem install -v . |
cocoapods | Version of CocoaPods that will be passed to gem install -v . |
Example of workflow using defaults.tools
:
name: Set up custom versions
defaults:
tools:
node: latest
yarn: '2'
fastlane: 2.224.0
on:
push:
branches: ['*']
jobs:
setup:
steps:
- name: Check Node version
run: node --version # should print a concrete version, like 23.9.0
- name: Check Yarn version
run: yarn --version # should print a concrete version, like 2.4.3
Control flow
You can control when a job runs with the needs
and after
keywords. In addition, you can use the if
keyword to control whether a job should run based on a condition.
jobs.<job_id>.needs
A list of job IDs whose jobs must complete successfully before this job will run.
jobs:
test:
steps:
- uses: eas/checkout
- uses: eas/use_npm_token
- uses: eas/install_node_modules
- name: tsc
run: yarn tsc
build:
needs: [test] # This job will only run if the 'test' job succeeds
type: build
params:
platform: ios
jobs.<job_id>.after
A list of job IDs that must complete (successfully or not) before this job will run.
jobs:
build:
type: build
params:
platform: ios
notify:
after: [build] # This job will run after build completes (whether build succeeds or fails)
jobs.<job_id>.if
The if
conditional determines if a job should run. When an if
condition is met, the job will run. When the condition is not met, the job will be skipped. A skipped job won't have completed successfully and any downstream jobs will not run that have this job in their needs
list.
jobs:
my_job:
if: ${{ github.ref_name == 'main' }}
Pre-packaged jobs
jobs.<job_id>.type
Specifies the type of pre-packaged job to run. Pre-packaged jobs produce specialized UI according to the type of job on the workflow's detail page.
jobs:
my_job:
type: build
Learn about the different pre-packaged jobs below.
build
Creates an Android or iOS build of your project using EAS Build.
jobs:
my_job:
type: build
params:
platform: ios | android # required
profile: string # optional, default: production
This job outputs the following properties:
{
"build_id": string,
"app_build_version": string | null,
"app_identifier": string | null,
"app_version": string | null,
"channel": string | null,
"distribution": "internal" | "store" | null,
"fingerprint_hash": string | null,
"git_commit_hash": string | null,
"platform": "ios" | "android" | null,
"profile": string | null,
"runtime_version": string | null,
"sdk_version": string | null,
"simulator": "true" | "false" | null
}
Build jobs can be customized so that you can execute custom commands during the build process. See Custom builds for more information.
deploy
Deploys your application using EAS Hosting.
jobs:
my_job:
type: deploy
params:
alias: string # optional
prod: boolean # optional
fingerprint
Calculates fingerprint of Android and iOS builds.
jobs:
my_job:
type: fingerprint
This job type only supports CNG (managed) workflows. If you commit your android or ios directories, the fingerprint job won't work.
This job outputs the following properties:
{
"android_fingerprint_hash": string,
"ios_fingerprint_hash": string,
}
get-build
Retrieves an existing build from EAS that matches the provided parameters.
jobs:
my_job:
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
This job outputs the following properties:
{
"build_id": string,
"app_build_version": string | null,
"app_identifier": string | null,
"app_version": string | null,
"channel": string | null,
"distribution": "internal" | "store" | null,
"fingerprint_hash": string | null,
"git_commit_hash": string | null,
"platform": "ios" | "android" | null,
"profile": string | null,
"runtime_version": string | null,
"sdk_version": string | null,
"simulator": "true" | "false" | null
}
submit
Submits an Android or iOS build to the app store using EAS Submit. For environment
, it uses the same environment used to create the build referenced in build_id
.
Submission jobs require additional configuration to run within a CI/CD process. See our Apple App Store CI/CD submission guide and Google Play Store CI/CD submission guide for more information.
jobs:
my_job:
type: submit
params:
build_id: string # required
profile: string # optional, default: production
update
Publishes an update using EAS Update.
jobs:
my_job:
type: update
params:
message: string # optional
platform: string # optional - android | ios | all, defaults to all
branch: string # optional
channel: string # optional - cannot be used with branch
maestro
Runs Maestro tests on a build.
maestro:
type: maestro
environment: production | preview | development # optional, defaults to preview
image: string # optional, defaults to 'default'. See https://docs.expo.dev/build-reference/infrastructure/ for a list of available images.
params:
build_id: string # required
flow_path: string | string[] # required
Custom jobs
Runs custom code and can use built-in EAS functions. Does not require a type
field.
jobs:
my_job:
steps:
# ...
jobs.<job_id>.runs_on
Specifies the worker that will execute the job. Available only on custom jobs.
jobs:
my_job:
runs_on: linux-medium | linux-large |
linux-medium-nested-virtualization |
linux-large-nested-virtualization |
macos-medium | macos-large # optional, defaults to linux-medium
Worker | vCPU | Memory (GiB RAM) | SSD (GiB) | Notes |
---|---|---|---|---|
linux-medium | 4 | 16 | 14 | Default worker. |
linux-large | 8 | 32 | 28 | |
linux-medium-nested-virtualization | 4 | 16 | 14 | Allows running Android Emulators. |
linux-large-nested-virtualization | 4 | 16 | 28 | Allows running Android Emulators. |
macos-medium | 5 | 12 | 85 | Runs iOS jobs, including simulators. |
macos-large | 10 | 20 | 85 | Runs iOS jobs, including simulators. |
Note: For Android Emulator jobs, you must use a
linux-*-nested-virtualization
worker. For iOS builds and iOS Simulator jobs, you must use amacos-*
worker.
jobs.<job_id>.outputs
A list of outputs for the job. Job outputs are available to all downstream jobs that depend on this job. Set outputs in a step using the set-output
function.
In the example below, the set-output
function sets the output named test
to the value hello world
in job_1
's step_1
step. Later in job_2
, it's accessed in step_2
using needs.job_1.outputs.output_1
.
jobs:
job_1:
outputs:
output_1: ${{ steps.step_1.outputs.test }}
steps:
- id: step_1
run: set-output test "hello world"
job_2:
needs: [job_1]
steps:
- id: step_2
run: echo ${{ needs.job_1.outputs.output_1 }}
jobs.<job_id>.steps
A job contains a sequence of tasks called steps
. Steps can run commands.
jobs:
my_job:
steps:
- name: My first step
run: echo "Hello World"
jobs.<job_id>.steps.<step>.id
The id
property is used to reference the step in the job. Useful for using the step's output in a downstream job.
jobs:
my_job:
outputs:
test: ${{ steps.step_1.outputs.test }} # References the output from step_1
steps:
- id: step_1
run: set-output test "hello world"
jobs.<job_id>.steps.<step>.name
The human-friendly name of the step, which is displayed in the job's logs. When a step's name is not provided, the run
command is used as the step name.
jobs:
my_job:
steps:
- name: My first step
run: echo "Hello World"
jobs.<job_id>.steps.<step>.run
The shell command to run in the step.
jobs:
my_job:
steps:
- run: echo "Hello World"
jobs.<job_id>.steps.<step>.working_directory
The directory to run the command in. When defined at the step level, it overrides the jobs.<job_id>.defaults.run.working_directory
setting on the job if it is also defined.
jobs:
my_job:
steps:
- uses: eas/checkout
- run: pwd # prints: /home/expo/workingdir/build/my-app
working_directory: ./my-app
jobs.<job_id>.steps.<step>.uses
EAS provides a set of built-in reusable functions that you can use in workflow steps. The uses
keyword is used to specify the function to use. All built-in functions start with the eas/
prefix.
jobs:
my_job:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- uses: eas/prebuild
- name: List files
run: ls -la
Below is a list of built-in functions you can use in your workflow steps.
eas/checkout
Checks out your project source files.
jobs:
my_job:
steps:
- uses: eas/checkout
View the source code for the eas/checkout function on GitHub.
eas/install_node_modules
Installs node_modules using the package manager (bun, npm, pnpm, or Yarn) detected based on your project. Works with monorepos.
jobs:
my_job:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
View the source code for the eas/install_node_modules function on GitHub.
eas/prebuild
Runs the expo prebuild
command using the package manager (bun, npm, pnpm, or Yarn) detected based on your project with the command best suited for your build type and build environment.
jobs:
my_job:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- uses: eas/prebuild
jobs:
my_job:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- uses: eas/resolve_apple_team_id_from_credentials
id: resolve_apple_team_id_from_credentials
- uses: eas/prebuild
with:
clean: false
apple_team_id: ${{ steps.resolve_apple_team_id_from_credentials.outputs.apple_team_id }}
Property | Type | Description |
---|---|---|
clean | boolean | Optional property defining whether the function should use --clean flag when running the command. Defaults to false. |
apple_team_id | string | Optional property defining Apple team ID which should be used when doing prebuild. It should be specified for iOS builds using credentials. |
View the source code for the eas/prebuild function on GitHub.
eas/send_slack_message
Sends a specified message to a configured Slack webhook URL, which then posts it in the related Slack channel.
The message can be specified as plaintext or as a Slack Block Kit message.
With both cases, you can reference build job properties and use other steps outputs in the message for dynamic evaluation.
For example, 'Build URL: ${{ eas.job.expoBuildUrl }}'
, Build finished with status: ${{ steps.run_fastlane.status_text }}
, Build failed with error: ${{ steps.run_gradle.error_text }}
.
Either "message" or "payload" has to be specified, but not both.
jobs:
my_job:
steps:
- uses: eas/send_slack_message
with:
message: 'This is a message to plain input URL'
slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'
Property | Type | Description |
---|---|---|
message | string | The text of the message you want to send. For example, 'This is the content of the message' .Note: Either message or payload needs to be provided, but not both. |
payload | string | The contents of the message you want to send which are defined using Slack Block Kit layout. Note: Either message or payload needs to be provided, but not both. |
slack_hook_url | string | The previously configured Slack webhook URL, which will post your message to the specified channel. You can provide the plain URL like slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]' , use EAS secrets like slack_hook_url: ${{ eas.env.ANOTHER_SLACK_HOOK_URL }} , or set the SLACK_HOOK_URL secret, which will serve as a default webhook URL (in this last case, there is no need to provide the slack_hook_url property). |
View the source code for the eas/send_slack_message function on GitHub.
eas/use_npm_token
Configures Node package managers (bun, npm, pnpm, or Yarn) for use with private packages, published either to npm or a private registry.
Set NPM_TOKEN
in your project's secrets, and this function will configure the build environment by creating .npmrc with the token.
jobs:
my_job:
name: Install private npm modules
steps:
- uses: eas/checkout
- uses: eas/use_npm_token
- name: Install dependencies
run: npm install # <---- Can now install private packages
View the source code for the eas/use_npm_token function on GitHub.