Introduction
Building and deploying mobile applications has traditionally required complex local toolchains, platform-specific SDKs, and hours of configuration. Expo's EAS (Expo Application Services) fundamentally changes this paradigm by providing a cloud-native build and submission pipeline that eliminates the need for local Xcode or Android Studio installations. Whether you're a solo developer shipping your first app or a team managing continuous delivery across platforms, EAS provides the infrastructure to build, submit, and update your React Native apps with unprecedented speed and reliability.
In this comprehensive guide, we'll explore every facet of EAS Build and Submit — from initial configuration through advanced workflows including custom build profiles, over-the-air updates, and production-grade CI/CD integration. You'll learn how to leverage EAS to streamline your entire mobile delivery pipeline.
Understanding EAS: Core Concepts and Architecture
EAS Build is a hosted service that compiles your React Native application in the cloud, producing signed .apk, .aab, or .ipa files ready for distribution. Unlike Expo's classic build system (expo build), EAS Build uses native toolchains running on remote machines, giving you the flexibility of a bare workflow with the convenience of managed infrastructure.
The EAS Ecosystem
The EAS platform consists of three core services that work together to form a complete delivery pipeline:
EAS Build handles compilation. It spins up a clean macOS (for iOS) or Linux (for Android) environment, installs dependencies, runs native code generation, and produces platform-specific artifacts. Each build runs in an isolated container with full access to native SDKs, CocoaPods, Gradle, and signing credentials.
EAS Submit automates app store uploads. After a successful build, Submit takes the artifact and pushes it directly to Apple App Store Connect or Google Play Console, eliminating the manual upload step that traditionally required Transporter or the Play Console web interface.
EAS Update provides over-the-air (OTA) JavaScript bundle updates. When you push a JavaScript-only change, EAS Update delivers it to installed apps without requiring a new native build or app store review — enabling instant bug fixes and feature rollouts.
How EAS Build Differs from Classic Builds
The classic expo build:android / expo build:ios commands used a shared, monolithic build environment that couldn't accommodate custom native modules or complex configurations. EAS Build runs your actual project with full native code support, meaning you can use any native dependency without ejecting.
Key differences include:
- Native code support: EAS builds from your actual native directories (
android/andios/) - Custom build profiles: Different configurations for development, staging, and production
- Credential management: Automatic handling of keystores, provisioning profiles, and certificates
- Build caching: Dependency caching to speed up subsequent builds
- Local builds: The ability to run builds on your own machine using
--local
Architecture and Build Configuration
The eas.json Configuration File
Every EAS project uses an eas.json file at the project root to define build profiles, submission settings, and update channels. This file is the central nervous system of your delivery pipeline.
{
"cli": {
"version": ">= 7.0.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": { "simulator": true },
"android": { "buildType": "apk" }
},
"preview": {
"distribution": "internal",
"channel": "preview",
"ios": { "autoIncrement": true },
"android": { "autoIncrement": true }
},
"production": {
"channel": "production",
"autoIncrement": true,
"ios": {
"resourceClass": "m-medium",
"distribution": "store"
},
"android": {
"buildType": "app-bundle",
"gradleCommand": ":app:bundleRelease"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "your-apple-id@example.com",
"ascAppId": "1234567890",
"appleTeamId": "ABCDE12345"
},
"android": {
"serviceAccountKeyPath": "./google-services.json",
"track": "production"
}
}
}
}Build Profile Inheritance
Build profiles support inheritance through the extends property, allowing you to define a base configuration and override specific settings for each environment. This DRY approach prevents configuration drift between environments and ensures consistency across your delivery pipeline.
{
"build": {
"base": {
"env": { "API_URL": "https://api.example.com" },
"cache": { "cacheDefaultPaths": true }
},
"staging": {
"extends": "base",
"channel": "staging",
"env": { "API_URL": "https://staging-api.example.com" }
},
"production": {
"extends": "base",
"channel": "production",
"env": { "API_URL": "https://api.example.com" }
}
}
}Credential Management
EAS automatically manages your signing credentials through its credential management system. When you run your first build, EAS prompts you to either generate new credentials or upload existing ones.
For iOS, EAS manages distribution certificates, provisioning profiles, and push notification keys. For Android, it handles keystore files and Google Play service account credentials. Credentials are stored encrypted on Expo's servers and can be shared across team members with appropriate access controls.
Step-by-Step Implementation
Installation and Initial Setup
Install the EAS CLI globally and log in to your Expo account:
npm install -g eas-cli
eas loginNavigate to your project directory and initialize EAS:
cd your-react-native-project
eas build:configureThis command creates the eas.json file and detects your project's configuration. For managed Expo projects, it automatically configures appropriate settings. For bare workflow projects, it generates configuration compatible with existing native directories.
Creating a Development Build
Development builds include the Expo dev client, enabling you to use Expo's development tools with custom native modules:
# Build for iOS simulator
eas build --profile development --platform ios
# Build for Android emulator
eas build --profile development --platform android
# Build for both platforms simultaneously
eas build --profile development --platform allThe build process uploads your project source to EAS servers (excluding node_modules and build artifacts), provisions a clean build environment, installs dependencies with caching, generates native code, compiles the native application, signs the artifact, and provides a download URL.
Submitting to App Stores
After a successful production build, submit directly to the app stores:
# Submit the latest iOS build
eas submit --platform ios --latest
# Submit a specific build by ID
eas submit --platform android --id build-id-here
# Submit both platforms
eas submit --platform all --latestEAS Submit handles the entire upload process including binary upload to App Store Connect or Google Play Console, artifact format validation, and release track configuration.
Real-World Use Cases
Use Case 1: Multi-Environment CI/CD Pipeline
A typical production app needs separate builds for development, staging, and production. EAS Build profiles make this straightforward with GitHub Actions:
# .github/workflows/eas-build.yml
name: EAS Build
on:
push:
branches: [main, staging]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Determine profile
id: profile
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "profile=production" >> $GITHUB_OUTPUT
else
echo "profile=staging" >> $GITHUB_OUTPUT
fi
- run: eas build --profile ${{ steps.profile.outputs.profile }} --platform all --non-interactive --no-waitUse Case 2: Internal Testing with QR Code Distribution
For distributing development builds to testers without going through app stores, configure an internal distribution profile:
{
"build": {
"preview": {
"distribution": "internal",
"android": { "buildType": "apk" },
"ios": { "simulator": false, "distribution": "internal" }
}
}
}Running eas build --profile preview --platform android generates an installable APK with a QR code that testers scan to download directly on their devices — no TestFlight or Google Play beta track required.
Use Case 3: Over-the-Air Update Rollout
EAS Update enables instant JavaScript updates without rebuilding native code:
# Publish an update to the production channel
eas update --channel production --message "Fix: resolved crash on login screen"
# Publish to a specific branch
eas update --branch staging --message "Feature: new checkout flow"Configure runtime version policies in eas.json to ensure update compatibility:
{
"build": {
"production": {
"channel": "production",
"runtimeVersion": { "policy": "sdkVersion" }
}
}
}Best Practices for Production
-
Use auto-increment for version management: Set
"autoIncrement": truein build profiles to automatically bump version numbers, preventing App Store rejection for duplicate version strings. -
Implement proper caching strategies: Enable
cacheDefaultPathsand define custom cache patterns to reduce build times from 15+ minutes to under 5 minutes for incremental builds. -
Separate signing credentials by environment: Use different keystores and provisioning profiles for development and production to prevent accidental production releases from development builds.
-
Use build metadata for traceability: Add custom metadata to each build for audit trails:
eas build --platform all --message "Release v2.5.0 - checkout redesign". -
Implement phased rollouts: When submitting to Google Play, use the
trackparameter to control rollout percentages — start at 10%, monitor crash rates, then gradually increase to 100%. -
Monitor build queue times: During peak hours, EAS build queues can extend significantly. Schedule critical builds during off-peak hours and use
--waitflags in CI to handle queue delays gracefully. -
Set up webhook notifications: Configure EAS webhooks to post build status updates to Slack or your monitoring system, ensuring your team stays informed without constantly checking the dashboard.
-
Use
--localbuilds for sensitive configurations: When working with proprietary native modules or when you need full control over the build environment, useeas build --localto compile on your own machine.
Common Pitfalls and Solutions
| Pitfall | Impact | Solution |
|---|---|---|
| Missing native directory when switching from managed to bare | Build fails with cryptic errors | Run expo pregenerate before configuring EAS to scaffold native directories |
| Provisioning profile mismatch on iOS | Build succeeds but app crashes on launch | Regenerate credentials with eas credentials and ensure team ID is correct |
| Gradle cache corruption causing Android build failures | Intermittent build failures with no clear error | Clear cache with eas build --clear-cache and set cacheDefaultPaths: false temporarily |
| OTA update incompatible with native version | App crashes after update is applied | Implement proper runtime version policies and test updates against specific native versions |
| Build queue timeout in CI pipelines | CI workflow hangs indefinitely waiting for build | Set --timeout 30 and implement retry logic with exponential backoff |
| Environment variables not available during build | App uses wrong API endpoints at runtime | Define env in the build profile directly, not just in .env files that aren't uploaded |
Performance Optimization
EAS Build performance depends on dependency caching, build environment resources, and project size. Here are strategies to minimize build times:
Intelligent Caching Configuration
{
"build": {
"production": {
"cache": {
"cacheDefaultPaths": true,
"customPaths": [
"ios/Pods",
"android/.gradle",
".metro-cache"
]
}
}
}
}Resource Class Selection
Upgrade to larger build machines for complex projects. The performance difference is substantial:
- iOS small (default): 12–18 minutes typical build time
- iOS medium: 8–12 minutes typical build time
- Android small (default): 8–12 minutes typical build time
- Android medium: 5–8 minutes typical build time
Parallel Platform Builds
Building both platforms with --platform all runs them in parallel rather than sequentially, effectively halving your total pipeline time for multi-platform delivery.
Comparison with Alternatives
| Feature | EAS Build | Fastlane | Bitrise | App Center |
|---|---|---|---|---|
| Setup Complexity | Low (CLI wizard) | High (Ruby config) | Medium (YAML) | Medium (GUI) |
| Native Code Support | Full | Full | Full | Full |
| OTA Updates | Built-in (EAS Update) | Manual (CodePush) | Manual | CodePush |
| Credential Management | Automatic | Manual (match) | Manual | Manual |
| Build Caching | Automatic | Custom scripting | Automatic | Automatic |
| App Store Submission | Built-in (EAS Submit) | Built-in (deliver) | Manual | Manual |
| Local Builds | Supported | Default | Not available | Not available |
Advanced Patterns and Techniques
Custom Build Hooks
Run custom scripts at specific points during the build process using preBuildCommand and postBuildCommand in your eas.json configuration. Common uses include generating environment-specific configuration files, running type checks, or notifying team members of build progress.
Multi-Target Builds
Build multiple app variants from a single codebase by using environment variables to control bundle IDs, app names, and API endpoints. This pattern is essential for white-label applications or apps with different branded versions.
Automated Submission with Build
Combine build and submission in a single command using --auto-submit, which triggers submission immediately after a successful build completes:
eas build --profile production --platform all --auto-submitTesting Strategies
Test your EAS configuration before committing to production builds:
# Run a local build to test configuration without consuming cloud build minutes
eas build --profile development --platform android --local
# Validate eas.json configuration for syntax errors
eas diagnostics
# Test OTA updates with a preview channel before production
eas update --branch preview --message "Test update"
# List recent builds to verify output
eas build:list --platform all --limit 5Write integration tests that verify OTA update behavior:
import { Updates } from 'expo-updates';
describe('OTA Updates', () => {
it('should check for updates on app launch', async () => {
const update = await Updates.checkForUpdateAsync();
expect(update.isAvailable).toBeDefined();
});
it('should download and apply updates when available', async () => {
const result = await Updates.fetchUpdateAsync();
if (result.isNew) {
await Updates.reloadAsync();
}
expect(result.isNew).toBeDefined();
});
});Future Outlook
The EAS platform continues to evolve with expanded resource class offerings, GPU-accelerated builds for compute-heavy projects, and deeper integration with the React Native new architecture. Expo is working on EAS Insights, a monitoring service providing real-time analytics on update adoption rates, crash reporting linked to specific builds, and performance metrics across the entire delivery pipeline. The vision is a unified platform where building, deploying, monitoring, and updating mobile applications is a single, seamless workflow.
EAS Build Optimization
Optimize your EAS Build times by caching dependencies and build artifacts. Configure the cache field in your eas.json to cache node_modules and CocoaPods between builds. Use the --local flag to build on your own machine for faster iteration during development. For production builds, use the --auto-submit flag to automatically submit to the app stores after a successful build. Monitor your build queue times on the EAS dashboard and consider upgrading to a higher-tier plan if build times are impacting your release velocity.
Conclusion
Expo EAS Build and Submit represents a paradigm shift in how React Native applications are built and delivered. By moving native compilation to the cloud, automating app store submissions, and enabling instant over-the-air updates, EAS eliminates the operational complexity that has historically plagued mobile development teams.
Key takeaways:
- EAS Build supports full native code — no need to choose between managed and bare workflows
- Build profiles enable multi-environment pipelines — development, staging, and production in one file
- Credential management is automatic — EAS handles keystores, certificates, and provisioning profiles
- EAS Submit eliminates manual uploads — direct submission to App Store Connect and Google Play Console
- Over-the-air updates enable instant delivery — push JavaScript changes without app store review
- CI/CD integration is first-class — GitHub Actions, CircleCI, and other platforms work seamlessly
- Caching and resource classes optimize build times — from 20+ minutes down to under 5 minutes
Start by running eas build:configure in your project and experiment with development builds. Once comfortable, graduate to automated CI/CD pipelines with EAS Update for instant delivery.