All posts
3 min readBy BetterCodePush

OTA updates for Expo prebuild and bare React Native, without buying the whole stack

You prebuilt your Expo app into native projects, or you're on bare React Native. You want over-the-air updates without adopting an entire managed platform. Here is how that works.

ExpoBare React NativeOTAPrebuild

Most writing about React Native OTA updates assumes one of two worlds: a fully managed Expo workflow, or a bare React Native CLI project. A lot of real teams live in between. You might be on bare React Native by choice, or you might use Expo prebuild (Continuous Native Generation) to generate native projects and use EAS Build while keeping control of the native side. Both of those teams want the same thing — over-the-air updates — without adopting an entire platform just to get them.

Why the hybrid feels awkward today

The prebuild-and-bare middle ground falls into a documentation gap. Guides written for managed Expo assume you use the managed update service end to end; guides written for bare RN assume you never touched Expo tooling at all. If you prebuilt an Expo app into native projects, neither set of instructions quite fits, and you are left stitching advice together.

What actually matters for OTA

Strip away the workflow branding and OTA needs the same three things regardless of how you produced your native projects:

  • A stable native binary that loads a JavaScript bundle at startup.
  • A way to publish JavaScript updates matched to that binary's app version.
  • Control over how those updates roll out, and a way to pull them back.

How you generated the native projects — bare from the start, or prebuilt from an Expo config — does not change any of that. What changes is which build integration wires the update runtime into your app.

How BetterCodePush fits

BetterCodePush owns the native bundle-loading path, and the initializer installs the right build integration for how your project is set up. For a prebuilt Expo app you point it at the prebuild flow; for a bare project you point it at the bare flow:

# Expo prebuild / CNG
npx bettercodepush init --build expo
npx expo prebuild --clean

# Bare React Native
npx bettercodepush init --build bare
npx pod-install

From there the release loop is identical: ship one native build, then deliver signed JavaScript updates to matching app versions. Your native projects, your CI, and your build service stay exactly where they are — you are adding an update channel, not migrating your whole workflow.

Compatibility notes

  • Expo SDK 52 and newer are supported through prebuild/CNG, including native binaries built with EAS Build.
  • Bare React Native CLI projects on iOS and Android are supported directly.
  • Expo Go and expo-updates/EAS Update are not used, because BetterCodePush owns the native bundle-loading path itself.

If you are coming from CodePush, the migration guide covers the cutover, and what you can and can't ship over the air explains where the JavaScript-versus-native line falls.

Ship your first update

Create an organization and deploy an over-the-air update in minutes. No billing setup during the beta.

Start with BetterCodePush