EAS Update in Production: Channels, Runtime Versions, Rollbacks
Run EAS Update safely in production: channels and branches, runtime versions, percentage rollouts, rollbacks, pricing, and fixes when updates don't arrive.
Full-Stack, Mobile & AI Engineer
Over-the-air updates are the closest thing a mobile team has to a web deploy. A bug that would take a day or two in App Review can be fixed for most users in minutes. They're also the fastest way to break every installed copy of your app at once, if you push an update to a build that can't run it.
EAS Update is Expo's service for shipping JavaScript and asset updates to apps that are already installed. This guide covers the parts that matter in production: how channels and branches route updates, how runtime versions keep updates compatible, how to roll out gradually and roll back, what it costs, and what to check when an update doesn't show up.
Key takeaways
- Over-the-air updates ship JavaScript and assets, not native code. Anything that changes native code needs a new store build.
- Builds listen to a channel; channels point to a branch; updates are published to a branch. That indirection is what makes staged releases and rollbacks possible.
- The runtime version guarantees compatibility. An update only reaches builds with the same runtime version. The
fingerprintpolicy is the most cautious choice. - Roll out gradually to a percentage of users, and roll back with one command if something breaks.
- By default, a downloaded update applies on the next app launch, which is the most common reason people think "the update didn't work".
What you can and can't update over the air
| Can ship over the air | Needs a new store build |
|---|---|
| JavaScript and TypeScript changes | New or upgraded native modules |
| Images, fonts and other bundled assets | Changes to permissions or Info.plist / manifest |
| Bug fixes in screens and business logic | Expo SDK or React Native upgrades |
| Copy, layout and style changes | App icon, splash screen, app name |
Store rules also apply: Apple allows downloaded interpreted code only when it doesn't change the app's primary purpose or bypass its security, so use updates for fixes and improvements within the app you shipped, not to turn it into a different app.
Channels, branches and builds
The routing model has three pieces:
- Build: each build is created with a
channel(set in the build profile ineas.json), such asproductionorpreview. - Channel: points to a branch. By default, the
productionchannel points to theproductionbranch. - Branch: an ordered list of updates. Publishing adds a new update to a branch.
eas update --branch production --message "Fix crash on checkout screen"Because channels point to branches, you can redirect a channel to a different branch without rebuilding, which is how branch-based rollouts work.
Runtime versions: the compatibility guarantee
Each build has a runtime version, and each update is published for a runtime version. An update is only delivered to builds whose runtime version matches. That's what stops a JavaScript bundle expecting a new native module from reaching an old build that doesn't have it.
You choose a policy in your app config:
| Policy | Runtime version changes when | Trade-off |
|---|---|---|
appVersion | You change the app version | Simple, but forgetting to bump the version can cause mismatches |
fingerprint | Anything that may affect the native runtime changes | Most cautious; creates new runtime versions more often |
nativeVersion | The native version and build number change | Ties updates to each native build |
{
"expo": {
"runtimeVersion": { "policy": "fingerprint" }
}
}If you're unsure, fingerprint is the safest default, because it changes automatically whenever native code does.
How updates reach users
Out of the box, the app checks for an update when it launches, downloads it in the background and applies it the next time the app starts. Users therefore often need two launches to see a fix. For urgent fixes, you can check, download and reload explicitly with the expo-updates API, for example after a user returns to the app, but avoid interrupting someone mid-task.
Publishing an update
One command builds the JavaScript bundle and assets and publishes them to the branch behind a channel:
eas update --channel production --environment production --message "Fix rounding in checkout total"The --environment flag is required: it decides which EAS environment variables are used, and only that environment's variables are available while the update is built. Because EXPO_PUBLIC_ values are compiled into the JavaScript, an update carries whatever values that environment held at publish time, so check them before publishing to production. Anything secret belongs on your server, not in the app.
Updates from GitHub Actions
Publishing from CI makes every update traceable to a commit. A workflow using Expo's GitHub Action:
name: Publish preview update
on:
push:
branches: [main]
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas update --channel preview --environment preview --message "${{ github.event.head_commit.message }}" --non-interactivePublish to a preview channel on every merge, test it on real devices, then promote it to production deliberately, rather than shipping every merge straight to users.
Rolling out gradually
Per-update rollout: publish to a percentage of users first.
eas update --branch production --rollout-percentage=10 --message "New checkout validation"Increase the percentage with eas update:edit (100 completes it), or undo it with eas update:revert-update-rollout.
Branch-based rollout: eas channel:rollout sends a percentage of a channel's users to a different branch, which suits larger changes made of several updates.
Rolling back
If an update causes problems, run:
eas update:rollbackThe interactive guide lets you roll back to a previously published update or tell clients to use the update embedded in the build. When you publish again later, clients receive the new update as normal. expo-updates can also detect an error on launch and fall back to the previously working update on its own, but don't rely on that as your only safety net.
From the field: OTA hotfixes are part of the release path I own, alongside store review, phased rollouts and crash triage in Sentry. The rule that makes them safe is simple: an update goes to a small percentage first, and crash reports decide whether it goes wider.
Self-hosting updates without EAS
expo-updates, the library inside the app, doesn't require Expo's servers. It works with any server that implements the Expo Updates protocol: point updates.url in your app config at your server and set a runtimeVersion, and Expo publishes an example implementation (the Custom Expo Updates Server repository) to start from. The trade-off is operational: you then own storage and delivery, update signing, rollouts, rollbacks and monitoring. Self-hosting makes sense for strict data-residency rules or very large audiences where per-user pricing outweighs the engineering time; for most teams, EAS is cheaper than running it yourself.
The same library also works in bare React Native apps that don't otherwise use Expo: add Expo modules support to the project, then install expo-updates.
What it costs (October 2026)
The service is priced by monthly active users who receive updates. The Free plan includes 1,000 MAUs, Starter ($19/month) includes 3,000, and Production ($199/month) includes 50,000, with additional MAUs at $0.005 each up to 200,000. Check Expo's pricing page for current numbers.
If you'd rather host updates yourself, the Expo Updates protocol is open, so expo-updates can talk to a self-hosted update server. Most teams find the managed service cheaper than running their own.
Why isn't my EAS update showing up?
| Check | What to look for |
|---|---|
| Runtime version | Does the update's runtime version match the installed build's? |
| Channel and branch | Is the build on the channel you think, and does it point to the branch you published to? |
| Launch timing | Updates apply on the next launch by default. Close and reopen the app twice. |
| Build type | Development builds behave differently; test with a release or preview build |
| Environment variables | Values are inlined when you publish; publish with the same variables as the build |
| Rollout percentage | Is the device outside the current rollout percentage? |
FAQ
Do over-the-air updates follow App Store rules?
Yes, when used for fixes and improvements within the app's existing purpose. Apple allows downloaded interpreted code as long as it doesn't change the app's primary purpose or bypass security features, and Google Play has a similar exception for code run in an interpreter. Native changes still go through store review.
What is a runtime version?
It's a label that describes the native code an update depends on. Builds and updates share a runtime version, and an update is only delivered to builds with a matching one, so JavaScript never runs on a build missing the native code it needs.
How do I roll back an EAS update?
Run eas update:rollback and choose whether to roll back to a previously published update or to the update embedded in the build. If you used a percentage rollout, you can also revert it with eas update:revert-update-rollout.
Is EAS Update free?
There's a free plan covering 1,000 monthly active users receiving updates. Paid plans include more users (3,000 on Starter, 50,000 on Production) and charge per additional monthly active user beyond that.
Next steps
If you want over-the-air updates set up with sensible runtime versions, staged rollouts and a rollback plan, I can configure it with your build pipeline. Get in touch, or see my services.
Sources
- Environment variables in EAS Update, Expo
- Custom updates server, Expo
- expo-github-action, GitHub
- EAS Update introduction, Expo
- Runtime versions, Expo
- Rollouts, Expo
- Rollbacks, Expo
- Expo pricing, Expo
- App Review Guidelines (2.5.2), Apple
Related
A CI/CD Pipeline for React Native: EAS, GitHub Actions, and Fastlane
A React Native CI/CD pipeline for 2026: checks on every pull request, preview builds, and store releases with EAS Workflows, GitHub Actions or Fastlane.
App Store Rejection Reasons in 2026, and How to Fix Each One
Apple rejected 2.09M submissions in 2025. The App Store rejection reasons that come up most, what each guideline means, and how to fix them fast.
The App Store and Google Play Pre-Submission Checklist (2026)
A copyable App Store and Google Play submission checklist for 2026: build, access, privacy, payments, listing and launch-day steps, in order.
