MD. Shahadot Hossain
Back to Knowledge Hub
Oct 11, 20265 min read

React Native OTA Updates with Expo EAS Update

Ship JavaScript fixes to production in minutes: runtime versions, channels, safe rollouts and common mistakes.

react-nativeexpootamobile

Ship a fix to production in minutes instead of waiting days for app store review. Over-the-air (OTA) updates let a React Native or Expo app download a new JavaScript bundle and assets after it is already installed. This guide covers what OTA can and cannot change, how Expo's EAS Update is wired together, a safe release workflow, and the mistakes that cause crashes in production.

What OTA can and cannot update

An app binary has two layers. OTA only replaces the top one.

LayerExamplesUpdated by OTA?
JavaScript and assetsScreens, business logic, styles, images, fonts bundled with JSYes
Native codeNew native modules, permissions, Info.plist / AndroidManifest.xml, SDK upgrades, app icon, splashNo, needs a new store build

If a change touches anything in the second row, ship a new binary. OTA is for fast, low-risk JavaScript changes.

How EAS Update fits together

Four ideas decide who receives which update.

  • Runtime version: a label for the native layer of a build. An update is only delivered to builds with the same runtime version, so JavaScript never runs against native code it was not written for.
  • Branch: a named stream of updates, like a git branch.
  • Channel: a name embedded in each build (for example production or preview). A channel points to a branch.
  • Update: one published JavaScript bundle plus assets on a branch.

Because the channel is baked into the build and points to a branch, you can change which updates a build receives without shipping a new binary.

Setup

npx expo install expo-updates
eas update:configure

eas update:configure adds the update URL and a runtime version policy to your app config. Pick the runtime policy deliberately:

{
  "expo": {
    "runtimeVersion": { "policy": "fingerprint" },
    "updates": { "url": "https://u.expo.dev/<your-project-id>" }
  }
}
PolicyRuntime version changes whenGood for
appVersionYou bump the app versionSimple apps with disciplined versioning
nativeVersionNative version or build number changesTeams that already bump build numbers
fingerprintThe native project actually changesSafest default: it detects native changes for you
sdkVersionExpo SDK changesManaged apps that only change native code via SDK upgrades

Then map build profiles to channels in eas.json:

{
  "build": {
    "preview": { "channel": "preview", "distribution": "internal" },
    "production": { "channel": "production" }
  }
}

Publishing updates

# Publish to the branch linked to a channel
eas update --channel production --message "Fix checkout total rounding"

# Publish to an explicit branch
eas update --branch staging --message "Try new onboarding"

# In CI: use the current git branch name as the update branch
eas update --auto

A good message states the user-visible change. You will read these when you need to find or roll back an update.

Controlling when users get the update

By default the app checks for an update at launch, downloads it in the background and applies it on the next cold start. That is safe but slow to reach users. When you want more control, use the expo-updates API:

import * as Updates from "expo-updates";
import { useEffect } from "react";

export function useApplyUpdates() {
  const { isUpdateAvailable, isUpdatePending } = Updates.useUpdates();

  useEffect(() => {
    if (isUpdateAvailable) Updates.fetchUpdateAsync();
  }, [isUpdateAvailable]);

  useEffect(() => {
    if (isUpdatePending) Updates.reloadAsync(); // restart into the new bundle
  }, [isUpdatePending]);
}

Reloading mid-task is jarring. Prefer applying on a natural boundary: app foreground, after a form is submitted, or behind an "Update ready, restart" prompt for large changes.

A safe release workflow

  1. Develop and test on a development or preview build.
  2. Publish to preview and test on a real device that has the preview build.
  3. Promote the same change to production with a clear message.
  4. Watch crash and error reporting for the first hour. An update that crashes at launch can affect every user on that channel.
  5. Roll back fast if needed by republishing the last good update to the branch, or rolling back to the embedded bundle. Check eas update --help and the EAS docs for the current rollback and gradual-rollout commands.

For high-risk changes, use a gradual rollout so only a percentage of users receive the update at first, then widen it.

Store policy

Apple and Google allow OTA updates of JavaScript and assets as long as you do not change the app's primary purpose or use updates to bypass store review. Keep OTA for fixes and iterative improvements, and ship anything that materially changes functionality through a normal review.

Common mistakes

  • Native change shipped as OTA. A new native module in an update crashes builds that do not contain it. Use the fingerprint policy so the runtime version changes automatically.
  • Wrong channel. Publishing to production while testing means real users get unfinished work. Make channel names part of your team vocabulary.
  • Assuming environment variables update. Variables inlined at build time stay in the binary. Values read when you run eas update are baked into that update. Know which is which.
  • No way to tell what is running. Show Updates.updateId, Updates.channel and Updates.runtimeVersion on an internal debug screen.
  • Forgetting old binaries. Users on an old store version keep their old runtime version, so they only receive updates published for it.

Bare React Native (without Expo)

Bare React Native apps can use expo-updates too, with extra native setup. Microsoft retired App Center, including hosted CodePush, in March 2025, so plan on EAS Update, a self-hosted update server that implements the Expo Updates protocol, or another maintained OTA provider.

Checklist

  • Runtime version policy chosen (prefer fingerprint)
  • Channels defined per build profile
  • Preview channel used before production
  • Update message describes the user-visible change
  • Crash reporting watched after each production update
  • Rollback steps written down and tried once
  • Debug screen shows update id, channel and runtime version