React Native Expo EAS 2026: eas build, eas init & OTA Updates
Master Expo Application Services (EAS): configure build profiles for development, preview, and production, deploy over-the-air updates with EAS Update, set up CI/CD with GitHub Actions, and manage environment variables securely.
Quick answer. Expo Application Services (EAS) is the production deployment platform for React Native apps. EAS Build runs native iOS and Android builds in the cloud with no local Xcode or Android Studio setup, and EAS Update ships JavaScript fixes over-the-air in minutes, bypassing App Store review for non-native changes. Together they remove the two biggest bottlenecks in shipping React Native apps.
Expo Application Services (EAS) is the production deployment platform for React Native apps. It solves the two hardest problems in mobile development: the slow native build cycle (EAS Build) and the week-long App Store review cycle for every bug fix (EAS Update).
EAS Update โ Expo's over-the-air update system โ lets you push JavaScript and asset changes to users instantly, bypassing App Store review for anything that doesn't touch native code.
Project Setup
# Initialize EAS in an existing Expo project
npx expo install expo-updates
eas init # Creates a project on expo.dev and writes project ID to app.json
# Install EAS CLI globally
npm install -g eas-cli
eas login
// app.json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"owner": "viprasol",
"runtimeVersion": {
"policy": "appVersion" // OTA updates target matching app version
},
"updates": {
"url": "https://u.expo.dev/[your-project-id]",
"enabled": true,
"fallbackToCacheTimeout": 30000
},
"android": {
"package": "com.viprasol.myapp",
"versionCode": 1
},
"ios": {
"bundleIdentifier": "com.viprasol.myapp",
"buildNumber": "1"
},
"extra": {
"eas": {
"projectId": "your-project-id-from-expo-dev"
}
}
}
}
EAS Build Profiles
// eas.json โ the EAS configuration file
{
"cli": {
"version": ">= 12.0.0",
"requireCommit": true // Fail if there are uncommitted changes
},
"build": {
// Development builds: includes Expo dev client, connects to Metro
"development": {
"developmentClient": true,
"distribution": "internal",
"channel": "development",
"env": {
"APP_ENV": "development"
},
"android": {
"buildType": "apk" // APK for direct install (faster than AAB)
},
"ios": {
"simulator": false,
"enterpriseProvisioning": "adhoc"
}
},
// Preview builds: production JS, internal distribution (TestFlight/Firebase)
"preview": {
"distribution": "internal",
"channel": "preview",
"env": {
"APP_ENV": "staging"
},
"android": {
"buildType": "apk"
},
"ios": {
"simulator": false
}
},
// Production builds: App Store / Google Play submission
"production": {
"distribution": "store",
"channel": "production",
"env": {
"APP_ENV": "production"
},
"android": {
"buildType": "app-bundle" // AAB required for Play Store
},
"ios": {
"autoIncrement": "buildNumber" // Auto-increment build number
},
"cache": {
"disabled": false,
"key": "production-v1"
}
}
},
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "./google-play-service-account.json",
"track": "internal", // Start in internal testing, promote manually
"releaseStatus": "draft"
},
"ios": {
"appleId": "your@apple-developer-email.com",
"ascAppId": "1234567890", // App Store Connect app ID
"appleTeamId": "TEAMID"
}
}
}
}
๐ Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different โ senior engineers only, direct Slack access, and a 5.0โ Upwork record across 1000+ projects.
- React, Next.js, Node.js, TypeScript โ production-grade stack
- Fixed-price contracts โ no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Environment Variables in EAS
Environment variables in EAS require a specific pattern to be embedded in the JS bundle:
// src/config/env.ts
// Use expo-constants for runtime config, process.env for build-time
import Constants from "expo-constants";
// Values available at runtime (from app.json extra or EAS)
interface AppConfig {
apiUrl: string;
sentryDsn: string;
analyticsKey: string;
environment: "development" | "staging" | "production";
}
function getConfig(): AppConfig {
const env = process.env.APP_ENV as AppConfig["environment"] ?? "development";
return {
// process.env is inlined at build time by Metro bundler
apiUrl: process.env.API_URL ?? "http://localhost:3000",
sentryDsn: process.env.SENTRY_DSN ?? "",
analyticsKey: process.env.ANALYTICS_KEY ?? "",
environment: env,
};
}
export const config = getConfig();
# Store secrets in EAS (encrypted, not in eas.json)
eas secret:create --scope project --name API_URL --value "https://api.viprasol.com"
eas secret:create --scope project --name SENTRY_DSN --value "https://..."
eas secret:create --scope project --name ANALYTICS_KEY --value "..."
# Secrets are automatically available as process.env during builds
# List all secrets (values hidden)
eas secret:list
EAS Update: Over-the-Air Updates
// src/hooks/useOTAUpdate.ts
// Check for and apply updates when app comes to foreground
import { useEffect } from "react";
import * as Updates from "expo-updates";
import { AppState, AppStateStatus, Alert } from "react-native";
export function useOTAUpdate() {
useEffect(() => {
// Don't run in development (Expo Go / dev client handles this differently)
if (__DEV__) return;
const subscription = AppState.addEventListener(
"change",
async (nextAppState: AppStateStatus) => {
if (nextAppState !== "active") return;
try {
const update = await Updates.checkForUpdateAsync();
if (!update.isAvailable) return;
// Download silently in background
await Updates.fetchUpdateAsync();
// For critical updates: reload immediately
// For non-critical: prompt user
Alert.alert(
"Update Available",
"A new version of the app is ready. Restart to apply.",
[
{ text: "Later", style: "cancel" },
{
text: "Restart Now",
onPress: async () => {
await Updates.reloadAsync();
},
},
]
);
} catch (error) {
// Update check failed โ non-fatal, app continues to work
console.warn("OTA update check failed:", error);
}
}
);
return () => subscription.remove();
}, []);
}
Update Channels and Rollout Strategy
# Channels map to build profiles in eas.json
# Publish an update to the preview channel
eas update --channel preview --message "Fix crash on settings screen"
# Publish to production with rollout percentage
eas update --channel production --message "v1.2.1 - Performance improvements" \
--rollout-percentage 10 # Start with 10% of users
# Monitor the rollout, then expand
eas update:view # Check error rates on the update
eas update --channel production --message "v1.2.1" --rollout-percentage 100
# Roll back if needed
eas update:republish --channel production --group <previous-update-group-id>

๐ Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4โ8 weeks, full platforms in 3โ5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
CI/CD with GitHub Actions
# .github/workflows/eas-build.yml
name: EAS Build and Update
on:
push:
branches:
- main # Triggers preview build + OTA update
- production # Triggers production build + submit
pull_request:
branches: [main]
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} # Authenticate with expo.dev
jobs:
# Run on every PR: type check + tests
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npx tsc --noEmit
- run: npm test -- --watchAll=false
# Push to main: publish OTA update to preview channel
update-preview:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- name: Install EAS CLI
run: npm install -g eas-cli
- name: Publish OTA update to preview
run: |
eas update \
--channel preview \
--message "Auto-update from commit ${{ github.sha }}" \
--non-interactive
# Push to production branch: build + submit
build-production:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/production' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- name: Install EAS CLI
run: npm install -g eas-cli
- name: Build for production
run: |
eas build \
--platform all \
--profile production \
--non-interactive \
--wait
- name: Submit to stores
run: |
eas submit \
--platform all \
--profile production \
--non-interactive \
--latest # Submit the build we just created
Runtime Version Strategy
The runtime version controls which OTA updates are compatible with which builds:
// Option 1: appVersion policy (recommended for most teams)
// OTA updates only target devices running the matching app version
// Update 1.2.x only reaches users on app version 1.2.x
{
"runtimeVersion": { "policy": "appVersion" }
}
// Option 2: nativeVersion policy
// OTA updates target matching native code hash
// More granular โ update reaches users with identical native code
{
"runtimeVersion": { "policy": "nativeVersion" }
}
// Option 3: Explicit string (full control)
// You manually bump when native code changes
{
"runtimeVersion": "1.2"
}
The rule: bump the runtime version when you change native code (new native module, updated expo version, modified android/ or ios/ directories). OTA updates don't require a runtime version bump.
Cost Comparison
| Approach | Build Time | OTA Updates | Monthly Cost |
|---|---|---|---|
| Expo Go (development) | Instant (no build) | N/A | Free |
| EAS Build free tier | 15โ30 min | 1,000 updates | Free |
| EAS Build production tier | 15โ30 min | Unlimited | $99/month |
| EAS Enterprise | Priority queue | Unlimited | Custom |
| Self-hosted (Turtle CLI) | 20โ40 min | N/A | ~$50/month infra |
Additional Resources
- React Native Performance Optimization โ JS thread, Hermes, Reanimated
- React Native Testing Strategies โ unit, integration, E2E
- React Native Offline-First Architecture โ offline sync
- Mobile Payment Integration โ Stripe, IAP
Video Tutorial: Expo EAS Build & Deployment
This 15-minute video walks through:
- Setting up EAS Build profiles for development, preview, and production
- Configuring OTA updates with EAS Update for instant deployments
- Automating builds and submissions with GitHub Actions
- Managing environment variables and secrets securely
Frequently Asked Questions
Q: What is Expo Application Services (EAS)?
A: Expo Application Services (EAS) is the production deployment platform for React Native apps. It includes EAS Build (native compilation in the cloud) and EAS Update (over-the-air deployments). EAS eliminates the need for local Xcode/Android Studio setup and enables instant app updates without App Store review.
Q: How do I set up EAS Update for instant deployments?
A: Install the expo-updates package, run eas init to configure your project on expo.dev, add the project ID to your app.json, and use eas update to push JavaScript changes instantly. Updates bypass App Store review for non-native code changes.
Q: What's the difference between EAS Build and native build?
A: EAS Build compiles native code in the cloud, eliminating the need for local Xcode or Android Studio installation. Native builds require local toolchain setup and take longer. EAS Build is significantly faster for CI/CD pipelines and distributed teams.
Q: How do I configure build profiles in EAS?
A: Define build profiles in your eas.json file for development, preview, and production environments. Each profile specifies native code versions, environment variables, and distribution settings (internal vs. app store).
Q: Can I use EAS with GitHub Actions for CI/CD?
A: Yes. Install eas-cli in your GitHub Actions workflow, authenticate with EXPO_TOKEN, and use eas build or eas update commands. This enables fully automated build and deployment pipelines on push to main or production branches.
Our Capabilities
EAS dramatically reduces the operational overhead of mobile app deployment. Our React Native development team sets up EAS Build profiles, configures OTA update channels for staged rollouts, integrates EAS into CI/CD pipelines, and manages App Store/Play Store submissions โ so your team ships features instead of managing mobile build infrastructure.
Related Reading:
- React TypeScript Best Practices โ Type-safe React Native code
- Development Containers โ Consistent dev environments
- gRPC vs REST APIs โ API design for mobile apps
- Web Development Services โ Full-stack capabilities
Mobile app development โ | Start a project โ
Expo EAS Pricing 2026: What eas Expo Plans Actually Cost
Planning a budget around Expo EAS pricing in 2026 means looking past the headline tiers. The free plan gives you a limited number of EAS Build minutes and queued builds each month, which is fine for early prototyping. Paid plans add concurrent builds, faster build queues, larger artifact storage, and higher OTA update bandwidth through EAS Update. As your eas Expo usage scales, the variables that move your bill are build concurrency, monthly build volume, and update delivery, not the base subscription alone. Self-hosted runners can offset Build minutes if you have the infrastructure. At Viprasol, our senior engineers help you model real EAS Build and EAS Update costs against your release cadence, then architect pipelines so you pay for throughput you genuinely need, with full ownership of every config.
Expo EAS & eas.json FAQ
What is EAS in Expo? EAS (Expo Application Services) is Expo's cloud build and submit service. EAS Build compiles your iOS/Android binaries in the cloud, EAS Submit uploads them to the App Store / Play Store, and EAS Update ships over-the-air JavaScript updates.
What is the eas.json file? eas.json is the configuration file at your project root that defines build profiles (development, preview, production) - environment variables, build types, credentials source, and submit settings for each profile.
How do I configure build profiles in eas.json? Each profile under the "build" key sets options like distribution, channel, and environment; "submit" profiles hold store credentials. Run the EAS CLI against a named profile to build or submit.
Building a React Native app with Expo? Talk to our mobile team.
External Resources
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.
Need a Modern Web Application?
From landing pages to complex SaaS platforms โ we build it all with Next.js and React.
Free consultation โข No commitment โข Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse โฅ90 scores, mobile-first design, and full source code ownership. Senior engineers only โ from architecture through deployment.