Expo EAS Update Release Process: Ship OTA Fixes Safely
An Expo EAS Update release process for over-the-air fixes: runtime version policy, channels, a preview check, staged rollouts, rollback, and Apple rules.
A safe Expo EAS Update release process has five parts: a runtime version policy so updates reach only compatible builds, one channel per environment, a preview check on real devices, a staged rollout to a slice of users, and a rollback you've already practised. JavaScript and asset fixes ship this way. Native changes still go through the stores.
TL;DR: Pick thefingerprintorappVersionruntime policy and write it down. Give preview and production their own channels. Publish to preview, walk the money paths on real phones, then roll out to production with--rollout-percentageand watch errors tagged with the update ID. Knoweas update:rollbackbefore you need it. Ship native changes through the stores.
Three of our products ship Expo apps on iOS and Android: NoteTube, GymFast, and Kraftr. Why we pick Expo in the first place is covered in why most of our apps are Expo. This post is only about releases: over-the-air updates are the fastest way to fix every installed copy of an app, and also the fastest way to break all of them.
What can an EAS Update change, and what needs a store build?
An EAS Update replaces the JavaScript bundle and assets in apps already installed. Screens, logic, text, styles, images and fonts can change over the air. Anything that touches native code, permissions, native config, or the Expo SDK version needs a new store build.
Expo's how it works page states the rule underneath: an update runs on a build only when the platform and the runtime version both match exactly. By default the app checks for an update when it opens. If the download finishes within the fallbackToCacheTimeout window it runs at once; otherwise it runs on the next launch.
| Change | Over the air? |
|---|---|
| A bug in a screen, a wrong label, a layout fix | Yes |
| New screens built only from JS libraries already in the build | Yes, within store rules |
| Images, fonts, and other bundled assets | Yes |
| Adding or upgrading a library with native code | No, new build |
| Permissions, app icon, or splash in native config | No, new build |
| Upgrading the Expo SDK or React Native | No, new build |
Do OTA updates break Apple's rules?
Not when you ship fixes and small improvements. Apple's Developer Program License Agreement lets an app download interpreted code as long as it doesn't change the app's primary purpose, doesn't bypass OS security features, and doesn't create a storefront for other apps. A JavaScript bundle is interpreted code.
So the line isn't technical, it's about intent. A fixed checkout bug is fine. A new feature the reviewer never saw, or turning a study app into something else, should go through review. When in doubt, cut a build.
Which runtime version policy should you use?
Use fingerprint if you want native changes detected for you, or appVersion if you prefer runtime versions that read like store versions and you'll bump them every time native code changes. Either works. What breaks apps is not choosing, or choosing and forgetting.
Expo's runtime versions guide describes both. The appVersion policy increments the runtime version whenever the app version increments, so forgetting a bump after a native change ships JavaScript that expects code the old binary doesn't have. The fingerprint policy changes whenever anything that may affect the native runtime changes. It catches that mistake, at the price of more frequent builds.
We'd rather pay in builds than in crashes, so a changed fingerprint is treated as a hard stop for an over-the-air release. You can also set a custom runtime version per platform, but then the discipline is entirely yours.
How should channels and branches be set up?
Give each environment its own channel, baked into the build: preview for testers, production for store builds. Publish updates to branches; each channel links to the branch with the same name by default. Never test an update by publishing to production "just to see".
# Publish to testers first (--environment is required from SDK 55)
eas update --channel preview --environment preview --message "Fix paywall price label"
# Point a channel at a different branch when you need to
eas channel:edit production --branch version-2.0
The second command is how you move a whole channel without rebuilding, for example to pin store builds to a release branch.
What goes on the release checklist?
Before any production update, confirm the change is JavaScript-only, publish it to preview, and test it on real devices against the oldest build that shares the runtime version. Walk the paths that make money or lose data. Then write a message that tells whoever rolls back what changed.
- Confirm it is JS-only. Read the diff for new native dependencies or config. With
fingerprint, a changed runtime version settles it. - Publish to
previewand install it on real phones, including an older Android device. - Walk the money paths: sign-in, paywall, the core feature, anything with payments.
- Check state survives a reload. An update applies on relaunch. GymFast saves an in-progress workout on the device so a dropped connection doesn't lose a session; that's the kind of state to check across an update.
- Write the
--message. It shows in the dashboard and ineas update:list.
How does a staged rollout work in EAS Update?
Publish the update to a percentage of the channel's users, watch errors, then raise it or revert it. Per-update rollouts are enough for most teams; branch-based rollouts exist for moving a channel to a whole new branch gradually.
The rollouts docs give the commands:
# Release to a tenth of production users
eas update --channel production --environment production \
--rollout-percentage=10 --message "Fix crash on empty quiz"
# Raise it, or set it to 100 to finish
eas update:edit
# Abandon it and return to the previous update
eas update:revert-update-rollout
One limit to plan around: only one update per branch can be rolling out at a time, so finish or revert a rollout before publishing the next update for that runtime version. For a production fix, a short first stage with someone watching errors, then a raise, is a sensible rhythm. Low-traffic apps need longer before the numbers mean anything.
How do you roll back a bad update?
Run eas update:rollback. Its interactive guide offers two choices: republish a previous known-good update, or tell clients to run the update embedded in the store build. After a rollback, the next update you publish goes to all clients as normal.
Expo's rollbacks page covers both. Rolling back to embedded is the bigger hammer, because it sends users back to whatever JavaScript shipped inside the binary. If your last store build is months old, that can be a long way back.
eas update:rollback
Practise one rollback on preview so the command isn't new on a bad day.
Who should be allowed to publish to production?
As few people as possible, and ideally nobody by hand. Anyone with the right token can change every installed copy of the app in minutes, so production publishing belongs in CI, behind a reviewed pull request, with a company-owned token.
- Publish production updates from CI, so each update is tied to a commit.
- Require a reviewed pull request on the release branch before CI publishes.
- Keep the Expo token in CI secrets, owned by the company account, not a developer's personal one.
- Log every publish with commit, author and message.
- Agree on quiet hours. A Friday-night update with nobody watching is how a small bug becomes a weekend outage.
What monitoring makes staged rollouts worth it?
Error reporting that knows which update caused each error. Without that, a rollout percentage only limits the damage; it can't tell you the damage happened. Tag every crash and error event with the running update's ID and channel.
The expo-updates library exposes Updates.updateId, the UUID of the running update, plus Updates.channel and Updates.runtimeVersion. Attach all three to your crash reporter's context at startup. Then build a view of errors per update and an alert for a jump after a publish. Our maintenance and support work for mobile apps covers this setup along with SDK upgrades.
When should you cut a store build instead?
When native code, permissions or the SDK changed, when the change is big enough that a reviewer would want to see it, or when you want everyone on a fresh base before a launch or campaign. Many teams cut a store build on a regular rhythm and use updates in between.
That rhythm also keeps the embedded bundle recent, so a rollback to embedded never takes users far back. EAS Update works in bare React Native projects too, through expo-updates, and the process is the same; the trade-offs between the two setups are on our Expo vs React Native CLI page.
FAQ
How long after publishing do users get an EAS Update?
By default the app checks on launch. If the new update downloads within the fallbackToCacheTimeout window, it runs straight away; otherwise it downloads in the background and runs on the next launch. You can also call checkForUpdateAsync, fetchUpdateAsync and reloadAsync yourself at a quiet moment.
Can one update go to both iOS and Android?
Yes, one publish can target both platforms, but each device only runs an update whose platform and runtime version match its build exactly. If iOS and Android builds have different runtime versions, the update reaches only the matching ones, which is a common reason an update seems to vanish.
What happens if we publish while a rollout is still running?
For the same branch and runtime version, you can't: only one per-update rollout runs per branch at a time. Raise it to 100 with eas update:edit or revert it with eas update:revert-update-rollout, then publish the next update.
Should a hotfix skip the preview channel?
No. A hotfix is exactly when a rushed change breaks something else. Publishing to preview and walking the money paths takes minutes, and a staged production rollout still limits the damage if something slips through. Skip steps only when a rollback would be worse than the bug.
Shipping a React Native or Expo app? See our React Native development work and ask us to review your release process.