This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Automate development builds with EAS Workflows
Edit page
Learn how to automate Android and iOS development builds with EAS Workflows and use fingerprints to skip rebuilds when only JavaScript changes.
Manually rebuilding a development client on every push to a GitHub repository can be slow. Team members or new contributors who pull native changes (a new module, a new permission, an SDK upgrade) wait for a fresh build before they can run the project.
Automating development builds on the main branch keeps a current build ready to install. We can run eas build:dev to install the latest compatible build. If the project fingerprint matches an existing build, EAS downloads that build instead of creating a new one.
Learning outcomes
- Automate development builds for Android and iOS with the
buildjob type - Use
fingerprintandget-buildjobs to skip rebuilding when native code hasn't changed - Add a custom unit testing job that gates the workflow on passing tests
1 requirement
1 requirement
EAS CLI prompts to generate credentials the first time we trigger a build for a profile. Trigger the development build manually for both Android and iOS so the credentials exist before the workflow runs:
- eas build --profile development --platform allThe build job type for development builds
The build job type is one of the pre-packaged jobs from EAS Workflows. Instead of defining each step of the build process, we use the build job type to run an EAS Build for Android and iOS.
For this chapter, we use the development build profile from our eas.json. We use this profile during Expo app development.
1
Add a build.yml
Inside .eas/workflows/, add a new file called build.yml. It uses the build job type with profile and platform parameters to create development builds for Android and iOS.
Add the following code to the build.yml file:
name: Development builds jobs: build_android: name: Build Android type: build params: platform: android profile: development build_ios: name: Build iOS type: build params: platform: ios profile: development
We add two jobs: build_android and build_ios. Both are of type build with different parameters for platform and build profile.
The params key for each job includes:
2
3
Verify on the EAS dashboard
On the EAS dashboard, notice that it says "Triggered manually" under the Workflow graph tab. Any time the Trigger shows Manual, that means the workflow was started with the eas workflow:run command.
The logs for each build appear inside the workflow interface. Click a build's ID to open its EAS Build page.
Jobs without dependencies run in parallel by default in EAS Workflows. In the above workflow, since bothbuild_androidandbuild_iosjobs don't have any dependencies, they start at the same time.
Wait for both builds to complete before moving to the next step. Once they complete, we can use the Install or Open with Orbit buttons, or download from Artifacts to install on our device/emulator/simulator.
Skip unnecessary builds with fingerprints
When our project has only TypeScript/JavaScript changes, the existing development build is still compatible, so we do not need a new one.
Expo Fingerprint automates this decision. It hashes the project's native characteristics (dependencies, native project files, configuration). If the hash matches an existing build, native code has not changed, and EAS skips the rebuild. If the hash is different, native code changed, and EAS creates a new build.
Let's update build.yml step by step to detect native changes and build only when needed.
1
Add a fingerprint job
The fingerprint pre-packaged job hashes the project's native characteristics and outputs a fingerprint hash for each platform. The environment field tells EAS which environment variable configuration to use. Note that environment and profile can share the same name but refer to different things. profile points to a build profile in eas.json (how to build). environment points to environment variables configured on EAS (which values to inject at build time).
Update build.yml to add the fingerprint job before the build jobs:
name: Development builds jobs: fingerprint: name: Fingerprint type: fingerprint environment: development build_android: # ... build_ios: # ...
The fingerprint job produces two outputs: android_fingerprint_hash and ios_fingerprint_hash. We use these hashes in the next step to check if a compatible build already exists.
2
Add get-build jobs
The get-build pre-packaged job checks if a build already exists for a given fingerprint hash and profile. If a matching build exists, it outputs a build_id. If not, build_id is empty.
In our workflow file, let's add get_android_build and get_ios_build jobs between fingerprint and the build jobs. Each one uses needs: [fingerprint] to wait for the fingerprint job to complete and access its output:
name: Development builds jobs: fingerprint: # ... get_android_build: name: Check for existing Android build needs: [fingerprint] type: get-build params: fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }} profile: development get_ios_build: name: Check for existing iOS build needs: [fingerprint] type: get-build params: fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} profile: development build_android: # ... build_ios: # ...
In the above workflow, get-build jobs receive the fingerprint hash from the previous job using the ${{ needs.fingerprint.outputs.* }} expression syntax.
3
Add conditional builds
The if field controls whether a job runs. The expression ${{ !needs.get_android_build.outputs.build_id }} means: run a job only if get-build did not find a matching build. The ! is a negation operator. If a compatible build already exists for this fingerprint, the build job is skipped.
Let's update each build job to depend on its get-build job and add the if condition:
name: Development builds jobs: fingerprint: # ... get_android_build: # ... get_ios_build: # ... build_android: name: Build Android needs: [get_android_build] if: ${{ !needs.get_android_build.outputs.build_id }} type: build params: platform: android profile: development build_ios: name: Build iOS needs: [get_ios_build] if: ${{ !needs.get_ios_build.outputs.build_id }} type: build params: platform: ios profile: development
4
Add a trigger and run the workflow
Let's add the on.push trigger to the workflow so it runs automatically on every push to main:
name: Development builds on: push: branches: ['main'] jobs: fingerprint: name: Fingerprint type: fingerprint environment: development get_android_build: name: Check for existing Android build needs: [fingerprint] type: get-build params: fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }} profile: development get_ios_build: name: Check for existing iOS build needs: [fingerprint] type: get-build params: fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} profile: development build_android: name: Build Android needs: [get_android_build] if: ${{ !needs.get_android_build.outputs.build_id }} type: build params: platform: android profile: development build_ios: name: Build iOS needs: [get_ios_build] if: ${{ !needs.get_ios_build.outputs.build_id }} type: build params: platform: ios profile: development
Let's assume we made some TypeScript/JavaScript changes to our Expo project, like changing a button's color or updating the text on a screen. After doing so, we can push a commit to the main branch to trigger the workflow from our GitHub repository:
- git add .- git commit -m "Update button label and tweak styling"- git push origin mainThis triggers a new workflow that completes in seconds. On the EAS dashboard, both Android and iOS builds are skipped because a matching build already exists from the previous section.
We pull the latest from the main branch and run npx expo start. The existing development build picks up the JS changes.
Custom jobs for unit testing
This section assumes Jest is set up in the project. For setup instructions, see Unit testing with Jest.
When working in a team, we want every change pushed to the main branch to pass tests before triggering a new build.
Without automation, the team has to remember to run tests locally before pushing to main. If they forget and the code has a failing test, the next workflow can create a build from broken code.
With EAS Workflows, we can automate this process by mixing a custom job with our existing pre-packaged jobs. This custom job runs before fingerprint and build jobs. If a test fails, EAS stops the workflow and creates no build.
1
Add a custom job for unit tests
In our build.yml workflow file, let's add a custom job that runs unit tests before the builds start:
name: Development builds on: push: branches: ['main'] jobs: run_tests: name: Run unit tests steps: - uses: eas/checkout # Check out the repo - uses: eas/install_node_modules - run: npx jest --ci fingerprint: name: Fingerprint needs: [run_tests] type: fingerprint environment: development get_android_build: # ... get_ios_build: # ... build_android: # ... build_ios: # ...
The custom run_tests job does the following:
uses: eas/checkoutchecks out the project source files from the GitHub repository. Custom jobs run on a new virtual machine, so the code is not available by default. This step is required before accessing any project files.uses: eas/install_node_modulesinstalls dependencies using the package manager detected in the project (bun, npm, pnpm, or Yarn). This is needed before running any commands that depend on packages from node_modules, like Jest.run: npx jest --ciexecutes the test suite. Therunkey runs any shell command, similar to theechocommand. The--ciflag tells Jest to run in CI mode, which skips watching for file changes and exits after running all tests.
Now, the fingerprint job has needs: [run_tests], so it only runs after the tests pass. If tests fail, EAS skips the rest of the workflow.
2
Trigger the workflow
Commit and push the updated workflow along with the test file to main:
- git add .- git commit -m "Add unit tests to development builds workflow"- git push origin mainAfter triggering the workflow, open the EAS dashboard and notice the flow of the jobs inside our workflow.
Summary
Chapter 2: Development builds
We automated development builds using pre-packaged jobs, added fingerprints to skip unnecessary rebuilds, and integrated unit tests as a custom job before the build pipeline.
In the next chapter, learn how to create preview builds with Slack notifications and PR preview updates.