Migrating from Hot Updater to BetterCodePush
A staged migration from self-hosted Hot Updater to BetterCodePush, including native cutover, channel mapping, old binaries, and infrastructure retirement.
Hot Updater is a strong choice when owning the update infrastructure is the point. Its documented plugin system lets a team choose its build, storage, and database layers, and it supports providers such as Cloudflare, AWS, Firebase, and Supabase. BetterCodePush is for the opposite operational choice: keep the React Native update workflow, but hand the control plane and bundle delivery to a hosted service.
The two products have a shared lineage: BetterCodePush's runtime descends from the MIT-licensed Hot Updater codebase. That makes several concepts familiar, but this is still a native runtime change. Treat the migration as a new app binary with a staged cutover, not as an endpoint swap.
First: should you migrate?
Move if the time spent operating storage, metadata, credentials, and an update endpoint is no longer buying you meaningful control. Stay on Hot Updater if you need air-gapped or private infrastructure, a custom storage or database plugin, Re.Pack or Rock support, or a data-residency arrangement BetterCodePush does not offer. Hot Updater's official introduction makes self-hosting and bundler choice central features; migrating trades that flexibility for a narrower hosted workflow.
What maps cleanly — and what does not
- Channels: keep names such as staging and production, then recreate them per BetterCodePush app.
- App-version targeting: map directly to explicit
--target-app-versionvalues. - Fingerprint targeting: there is no automatic one-for-one import. Record the native app versions represented by each live fingerprint and target those versions explicitly.
- Bundles and rollout history: remain in the old database. BetterCodePush starts a new release history; archive the old records if you need them for audits.
- Storage and database plugins: disappear from the app repository because BetterCodePush supplies the hosted control plane and CDN.
Step 1 — Inventory the live Hot Updater path
Before changing source, record the production channel, update strategy, current native app versions, signing setup, update endpoint, and last known-good bundle. Also note whether the app uses the standard HotUpdater.wrap integration or calls update APIs manually. Hot Updater documents both appVersion and fingerprintstrategies in its basic setup; the latter needs the explicit mapping described above.
Step 2 — Preserve the old service for old binaries
Stop routine Hot Updater releases while you prepare the replacement, but do not delete its Worker, storage bucket, database, signing material, or DNS. Installed binaries continue to call the update endpoint compiled into them. The new BetterCodePush binary cannot redirect those old clients after the fact, so the previous service must remain healthy until that population has aged out.
Step 3 — Replace the integration on a migration branch
Remove HotUpdater.wrap or any manual Hot Updater initialization from the app entry point. For Expo prebuild, remove the @hot-updater/react-native config plugin. Remove the Hot Updater packages listed in your own package.json, plushot-updater.config.ts and provider-only environment files after saving their secrets somewhere appropriate. Bare projects should also restore the normal React Native bundle provider before running the new initializer; use the native diff from your original Hot Updater integration instead of guessing at generated lines.
Then install and initialize BetterCodePush:
npm install --save-dev @bettercodepush/cli
# Bare React Native
npx bettercodepush init --build bare
npx pod-install # iOS
# Or Expo prebuild/CNG
npx bettercodepush init --build expo
npx expo prebuild --cleanThe initializer installs the runtime packages, creates the hosted-service configuration, and wires the native bundle provider. Do not try to run both update runtimes inside the same binary: each expects to control which JavaScript bundle React Native loads.
Step 4 — Verify and ship one native build
npx bettercodepush doctorResolve every reported integration error, test a release build with the embedded bundle, and submit the new binary to the App Store and Play Store. This store release is mandatory: an OTA update cannot replace the native update runtime that is already installed. Keep the first production rollout small enough that you can compare crash and startup behavior against the previous binary.
Step 5 — Publish a deliberately boring first update
npx bettercodepush deploy --platform ios \
--target-app-version 1.0.0 --channel production --rollout 10Use the version of the new store binary, not a Hot Updater bundle version or fingerprint. Start with a harmless, visible JavaScript change, confirm that the release reaches only the intended binary, and then expand it. Repeat the check on Android with its actual store version. The rollout and rollback playbook covers the operational checks.
Step 6 — Retire self-hosted infrastructure last
Watch adoption by native app version rather than choosing an arbitrary date. Once the old Hot Updater-enabled binaries are below your support threshold, export the bundle and release metadata you need, revoke provider credentials, and then decommission the endpoint, database, and storage. If a meaningful old-binary population remains, keep a minimal read-only update service available; deleting it early turns every update check from those installs into an avoidable failure.