Migrating from EAS Update to BetterCodePush
A careful EAS Update to BetterCodePush migration for Expo prebuild apps: remove one OTA runtime, ship one new binary, and cut over safely.
EAS Update is a capable update service, and plenty of Expo teams should keep using it. This guide is for the narrower case: an Expo prebuild app that wants BetterCodePush to own its OTA path while continuing to use the Expo SDK and, if desired, EAS Build. This is a native integration migration, not a bundle-hosting switch. Plan it like one.
Who should — and shouldn't — migrate
Consider migrating if your app uses Expo prebuild/CNG or committed native projects, you can ship a new store build, and you want a separate update runtime. BetterCodePush supports Expo SDK 52+ through prebuild/CNG; EAS Build can remain your build service.
Do not migrate just because EAS Update exists. Stay with EAS Update if you are happy with its runtime-version, channel, and branch model, or if you rely on Expo Go for OTA testing. BetterCodePush does not run in Expo Go. Also stay put if you cannot ship a new native build: an installed EAS Update binary cannot be redirected to BetterCodePush by an OTA update.
The non-negotiable constraint: one new store build
EAS Update configures expo-updates in the native build; Expo's setup writes the update URL and runtime version into app and, outside CNG, native configuration. See Expo's configuration guide. BetterCodePush owns a different native bundle-loading path, so both OTA runtimes cannot own the same binary. The cutover therefore requires one new iOS/Android build through your usual store process. Later JavaScript changes can use the new runtime; native code, configuration, and runtime changes still need another store build.
Before changing anything, record the EAS map
Capture the production build version, platform, EAS channel, branch, and runtime version for every binary you still support. In EAS Update, a channel is compiled into the build and linked to a branch; a runtime version is the compatibility gate between that build's native code and an update. Expo documents both the channel–branch relationship and runtime version compatibility.
This inventory is your cutover plan, not data to import. BetterCodePush targets a release to an explicit app version and channel. A practical mapping is: EAS production channel to BetterCodePush production channel; each supported EAS runtime/build to the corresponding iOS or Android app version. Do not assume an EAS branch name, runtime version, or historical bundle transfers automatically. Keep EAS history as an audit trail and publish a freshly verified BetterCodePush release from source.
Remove EAS Update deliberately
Make a migration branch and commit a clean baseline first. Remove the expo-updatesdependency with your package manager, and remove any Updates.* imports, hooks, or calls in your app. Then remove configuration that only exists for EAS Update:
updates.url, EAS Update request headers, and theruntimeVersionsetting from app config, unless another tool genuinely consumes it.extra.eas.projectIdonly when it is not used by another EAS service in your project.- EAS Build profile
channelfields only when they were used solely to route EAS Updates; keep the rest ofeas.jsonif you still use EAS Build or Submit.
With CNG, remove those app-config values and then regenerate native projects. Expo explains that prebuild --cleandeletes and recreates native directories, so run it only from a clean, committed branch. With committed native projects, remove the EAS Update entries that Expo's configurator added: Android's expo.modules.updates.EXPO_UPDATE_URL,EXPO_RUNTIME_VERSION, and update request-header metadata; and iOS'sEXUpdatesURL, EXUpdatesRuntimeVersion, andEXUpdatesRequestHeaders in Expo.plist. Also remove any customEXUpdates* settings you added. Compare against your baseline rather than deleting unrelated Expo configuration.
Install and initialize BetterCodePush
Create a BetterCodePush organization, one app per native platform, and a Live API key in the console. Then install the CLI and initialize the Expo integration:
npm install --save-dev @bettercodepush/cli
npx bettercodepush init --build expo
npx expo prebuild --clean
npx bettercodepush doctorThe initializer installs the Expo integration and writes the platform app IDs; the doctor command is the gate before you build. Do not run the initializer while expo-updatesis still installed: these runtimes are intentionally mutually exclusive.
Validate, then ship the replacement binary
Build an internal iOS and Android build from this branch, exercise the launch path and a small BetterCodePush release, and verify the running bundle identifier. When it is healthy, submit the replacement native binaries through your normal release process. The new binaries are the first ones eligible for BetterCodePush updates; existing EAS Update binaries remain on their old runtime.
npx bettercodepush deploy --platform ios \
--target-app-version 1.0.0 --channel productionReplace the example version and platform with the actual replacement binary. Begin with a small rollout and use the staged rollout playbook before widening it.
Run the two generations in parallel
Coexistence is possible across binary generations, not inside one binary. Keep EAS Update available for the old production builds while BetterCodePush serves only the new store build. Keep publishing compatible emergency fixes to the correct system for each generation, and document the ownership split in CI so a release is not sent to the wrong fleet. Once the EAS generation has aged out of the active fleet, archive its release process and revoke any credentials you no longer need.
Rollback during the migration
Keep the EAS path intact until the new native build is proven. For an EAS-side incident, Expo supports rolling back to a previous update or the build's embedded update; review itsrollback guidance and test any persistent-data compatibility first. For a BetterCodePush update, stop or roll back that release using the console or your verified release workflow. What you cannot roll back OTA is the native loader swap itself: moving a BetterCodePush binary back to EAS Update requires another store build.
Keep the useful parts of Expo
This migration removes EAS Update, not Expo. You can keep Expo modules, CNG, EAS Build, EAS Submit, and your existing store-release process. The goal is one OTA runtime per binary, a clear compatibility map, and a cutover you can explain during an incident.