Shipping & Release· 6 min read

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.

By

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 fingerprint policy 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 airNeeds a new store build
JavaScript and TypeScript changesNew or upgraded native modules
Images, fonts and other bundled assetsChanges to permissions or Info.plist / manifest
Bug fixes in screens and business logicExpo SDK or React Native upgrades
Copy, layout and style changesApp 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 in eas.json), such as production or preview.
  • Channel: points to a branch. By default, the production channel points to the production branch.
  • 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:

PolicyRuntime version changes whenTrade-off
appVersionYou change the app versionSimple, but forgetting to bump the version can cause mismatches
fingerprintAnything that may affect the native runtime changesMost cautious; creates new runtime versions more often
nativeVersionThe native version and build number changeTies 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-interactive

Publish 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:rollback

The 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?

CheckWhat to look for
Runtime versionDoes the update's runtime version match the installed build's?
Channel and branchIs the build on the channel you think, and does it point to the branch you published to?
Launch timingUpdates apply on the next launch by default. Close and reopen the app twice.
Build typeDevelopment builds behave differently; test with a release or preview build
Environment variablesValues are inlined when you publish; publish with the same variables as the build
Rollout percentageIs 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

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related