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.

ToolDescription
nodeVersion of Node.js installed via nvm.
yarnVersion of Yarn installed via npm -g.
pnpmVersion of pnpm installed via npm -g.
bunVersion of Bun installed by passing bun-v$VERSION to Bun install script.
ndkVersion of Android NDK installed through sdkmanager.
bundlerVersion of Bundler that will be passed to gem install -v.
fastlaneVersion of fastlane that will be passed to gem install -v.
cocoapodsVersion of CocoaPods that will be passed to gem install -v.

Example of workflow using defaults.tools:

.eas/workflows/publish-update.yml
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
WorkervCPUMemory (GiB RAM)SSD (GiB)Notes
linux-medium41614Default worker.
linux-large83228
linux-medium-nested-virtualization41614Allows running Android Emulators.
linux-large-nested-virtualization41628Allows running Android Emulators.
macos-medium51285Runs iOS jobs, including simulators.
macos-large102085Runs 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 a macos-* 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
eas/checkout source code

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.

example.yml
jobs:
  my_job:
    steps:
      - uses: eas/checkout
      - uses: eas/install_node_modules
eas/install_node_modules source code

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 }}
PropertyTypeDescription
cleanbooleanOptional property defining whether the function should use --clean flag when running the command. Defaults to false.
apple_team_idstringOptional property defining Apple team ID which should be used when doing prebuild. It should be specified for iOS builds using credentials.
eas/prebuild source code

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]'
PropertyTypeDescription
messagestringThe 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.
payloadstringThe 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_urlstringThe 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).
eas/send_slack_message source code

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.

example.yml
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
eas/use_npm_token source code

View the source code for the eas/use_npm_token function on GitHub.