Automate mobile CI/CD with EAS Workflows and custom builds

Product10 minutes read

Stanisław Chmiela

Stanisław Chmiela

Engineering

Jon Samp

Jon Samp

Product

Szymon Dziedzic

Szymon Dziedzic

Engineering

EAS Workflows lets you automate builds, tests, updates, and more in a single YAML file. Learn what's available out-of-the-box and how to customize when needed.

Orchestrate advanced workflows with EAS Workflows and custom build jobs

Since the beginning of EAS Build the build process' customizations were limited to what eas.json allowed. There was no way to skip or replace some of the steps with something specific to the project. Moreover, as helpful as build hooks were, they weren't the most convenient way to orchestrate advanced workflows before or after a build.

In 2023, we released a preview of a fully customizable build feature that allowed developers to replace parts of the build process or create entirely new build processes.

Today, these capabilities are part of EAS Workflows, Expo’s unified CI/CD orchestration system. EAS Workflows lets you define complete pipelines—including builds, tests, submissions, updates, and notifications—in a single YAML configuration.

Expo has deprecated GitHub build triggers in favor of EAS Workflows, which provides a more powerful and flexible orchestration platform.

Custom build job configurations in .eas/build define how individual build jobs execute. EAS Workflows configurations in .eas/workflows define how jobs—including build jobs—are orchestrated into complete pipelines.

Orchestrating mobile CI/CD pipelines with EAS Workflows

EAS Workflows is Expo’s CI/CD orchestration system that allows you to automate builds, testing, submissions, updates, and notifications using YAML-defined pipelines.

Workflows define when pipelines run and how jobs depend on each other. Build jobs—including custom build jobs—run as components within these pipelines.

Workflows are defined in .eas/workflows/*.yml and support triggers such as:

  • push and pull request events
  • scheduled runs
  • manual execution

This allows teams to automate their entire mobile delivery pipeline in a single system.

yaml
on:
push:
branches: ['main']
pull_request:
branches: ['main']
schedule:
cron: '0 2 * * 1-5' # Weekdays at 2 AM UTC

Any workflow can also be run manually with eas workflow:run, regardless of the on: configuration. Path filtering and label-based triggers (pull_request_labeled) are also supported — see the triggers documentation.

Customizable build use cases

We’ve spent the last few months talking with users about what they run and we’re excited that they now can run those steps as a part of their EAS Workflows pipelines. Here are a few examples:

  • Sending a Slack message to your team after a build completes to notify them that there’s a new build to download. (exampledocs)
  • Running Maestro end-to-end tests. (exampledocs)
  • Creating archives for multiple targets in a single pipeline. For example, building an Android production app (.aab) while also generating a preview build for testing
  • Automatically submitting builds to TestFlight or Google Play after successful builds (Android exampleiOS example)
  • Publishing over-the-air updates after successful validation

These build jobs and automation steps can be composed into complete pipelines using EAS Workflows orchestration.

Pre-packaged jobs: what's available out-of-the-box

Beyond custom build jobs, EAS Workflows ships with pre-packaged job types for common mobile CI/CD tasks. These require no custom configuration — just declare the job type in your YAML:

  • submit — Submit builds to the App Store or Google Play
  • testflight — Distribute to TestFlight groups with changelogs and beta review control
  • update — Publish OTA updates via EAS Update
  • deploy — Deploy web apps via EAS Hosting
  • maestro / maestro-cloud — Run Maestro E2E tests on emulators and simulators
  • fingerprint — Hash your project's native characteristics to detect when full builds are needed
  • get-build — Find an existing build matching a fingerprint, avoiding redundant builds
  • repack — Repackage JS onto an existing build in ~2 minutes instead of a full native rebuild
  • slack — Send Slack notifications via webhook
  • github-comment — Auto-post build links and QR codes to pull requests
  • require-approval — Gate production deploys behind manual approval
  • doc — Display Markdown notes in workflow logs

See the pre-packaged jobs documentation for full syntax and examples.

How to customize your builds

Initial Setup

Build profile configuration accepts a that specifies the custom build job configuration located under .eas/build. These custom build jobs can be executed independently or orchestrated as part of EAS Workflows pipelines.

Here’s an example:

json
{
"build": {
"production": {
// Builds with profile "production"
// will use .eas/build/production.yml file
"config": "production.yml"
},
// You can also specify platform-specific workflows
// by placing the property under platform-specific config.
"development": {
"android": {
// uses .eas/build/development-android.yml
"config": "development-android.yml"
},
"ios": {
// uses .eas/build/development-ios.yml
"config": "development-ios.yml"
}
}
}
}

Basic custom build workflow

The simplest Custom Builds workflow looks like this:

yaml
build:
steps:
- eas/build

The eas/build function builds your app using the configured build profile. This step can also be orchestrated as part of a larger EAS Workflows pipeline alongside testing, submission, and deployment jobs.

Bash Steps

Let’s add another step that is going to send a message to Discord once a build succeeds.

For Slack specifically, EAS Workflows has a built-in slack job type — no custom scripting needed. The run step shown below is useful for services without built-in support, like Discord or Microsoft Teams.

First, configure a Discord webhook. You can follow this Discord tutorial.

Once you have the webhook URL, you can add a run step to the flow:

yaml
build:
steps:
- eas/build
- run:
command: |
# Post a new message to Discord
# https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
curl \
-H 'Content-Type: application/json' \
-d '{"content": "New build succeeded! URL: ${ eas.job.expoBuildUrl }"}' \
https://discord.com/api/webhooks/... # your webhook URL here

The run command executes as a Bash script. You can use tools like curl, Fastlane, Node.js scripts, or any custom automation logic.

These steps can also be integrated into full EAS Workflows pipelines alongside other jobs like submission, testing, and notifications.

Custom JavaScript functions

Bash scripts are great for simple tasks. For advanced tasks we recommend writing a JS function.

This provides:

  • Typed access to build properties
  • Better reuse across workflows
  • Improved maintainability

Custom functions can be integrated into both custom build jobs and full EAS Workflows pipelines. Check out the “TypeScript functions” guide in our EAS Build docs to learn how to set it up.

Customizing eas/build

If you need deeper customization of the build process, you can replace the single eas/build function with its underlying steps.

This allows you to:

  • Unlock encrypted secrets
  • Modify build environments
  • Install dependencies dynamically
  • Run validation scripts before building

There are four main examples you can use as scaffold (2 platforms × absence/presence of credentials):

Once copied, apply the necessary modifications.

For instance, you could unlock files encrypted with git-crypt. To do that you would first add a GIT_CRYPT_KEY secret to your EAS project containing base64-encoded key. You can get it copied to clipboard by running in an unlocked environment :

Then, ensure your project is set up for Full Git Workflow (cli.requireCommit should be set to true in eas.json). Then you’re ready to add the following Bash script after eas/checkout step.

Using custom build jobs within EAS Workflows

EAS Workflows allows you to orchestrate complete pipelines using build jobs as components.

Example:

yaml
name: Build and Submit
on:
push:
branches: [main]
jobs:
build:
type: build
params:
platform: ios
profile: production
submit:
type: submit
needs: [build] # Only runs if build succeeds
params:
build_id: ${{ needs.build.outputs.build_id }}
notify:
type: slack
after: [build, submit] # Runs regardless of success or failure
params:
webhook_url: https://hooks.slack.com/services/...
message: 'Pipeline finished for ${{ needs.build.outputs.app_version }}'

needs means the job only runs if its dependency succeeded. after means the job runs regardless — useful for notifications and cleanup. This allows full automation of your mobile CI/CD process.

Migrating from legacy build orchestration

Existing custom build configurations continue to work and integrate directly into Workflows pipelines. Teams already using GitHub Actions or other CI systems don't need to abandon them — you can invoke EAS Workflows from your existing pipeline with eas workflow:run, letting Workflows handle mobile-specific jobs like iOS builds, code signing, and store submissions while your current CI handles linting, unit tests, and code review

What’s next for EAS services?

Building your project is just one step in delivering high-quality mobile applications. EAS Workflows provides a complete CI/CD orchestration system that enables teams to automate builds, testing, submissions, and updates in a single platform.

EAS Workflows handles the mobile-specific parts of your CI/CD pipeline — builds, code signing, submissions, and OTA updates, directly within your Expo project.

Custom build jobs continue to play an important role by enabling deep customization of build behavior within these pipelines.

We’re continuing to expand EAS Workflows with new job types, improved automation capabilities, and deeper integrations to help teams ship faster and more reliably.

We'd love your feedback as you build and automate your workflows. Here are some good next steps:

builds
workflow

Dive in, and create your first Expo project

Learn more