Migrate from CodePush / hot-updater
App Center, which hosted CodePush, was retired on 31 March 2025, and the old appcenter codepush
CLI no longer works. Microsoft open-sourced a standalone code-push-server, whose repository was
archived in May 2025. hot-updater is a popular self-hosted replacement. This page maps both to
dash-ota and lists what behaves differently.
Concept mapping
| CodePush / hot-updater | dash-ota |
|---|---|
codePush(App) / hot-updater's wrap() | <DashOtaProvider config={…}> |
deployment keys / channel | channel, compiled into each build flavour |
targetBinaryVersion / targetAppVersion | exact runtimeVersion gate + targetAppVersions |
codePush.sync() / check | useOtaUpdate().checkNow() |
notifyAppReady() (which sync() calls for you) / hot-updater's confirm on first render | markHealthy(), which nothing calls for you unless you set autoMarkHealthyMs |
InstallMode.IMMEDIATE / InstallMode.ON_NEXT_RESUME | no equivalent: an update applies on the next cold start, or right away when you call applyUpdate(true) |
code-push-server / your storage provider | your self-hosted backend (no storage URL on the client) |
code-push-standalone release-react / hot-updater CLI | dash-ota publish, from @dash-ota/cli |
Steps
- Backend: mount
dashOtaMiddleware(or run it standalone). Unlike a signed-URL model, dash-ota streams blobs through your API. The device gets a download token scoped to one release, sent in a header and reusable for 30 minutes by default. - Keys and native config: install the CLI (
npm i -D @dash-ota/cli), runnpx dash-ota keygenonce per environment, and embed each public key and theruntimeVersionin the matching build flavour. If you signed CodePush or hot-updater bundles, this key replaces that one. If you didn't, it's new: dash-ota has no unsigned mode, so it won't publish without a key. - Provider: replace the CodePush or hot-updater wrapper with
<DashOtaProvider>+useOtaUpdate(). - Ready signal: call
markHealthy()once your first real screen works. CodePush'ssync()callednotifyAppReady()for you and hot-updater confirms on first render; dash-ota doesn't, unless you setautoMarkHealthyMs. A normal session (the app reaches JavaScript, then goes to the background) isn't counted against a bundle, but a bundle that crashes after reaching JavaScript is. See the crash-loop breaker. - Install modes: drop
IMMEDIATEandON_NEXT_RESUME. By default a downloaded update runs on the next cold start; callapplyUpdate(true)to restart into it now. - Targeting: map
targetBinaryVersionto dash-ota'sruntimeVersion, the value that says which native build a bundle is compatible with. UsetargetAppVersionsfor marketing-version ranges. - CLI: replace your release command with
dash-ota publish. It needs--bundle-dirand--app-id, encrypts and signs the release, and uploads it. Compile to Hermes bytecode with your binary'shermesc(see Hermes).
What changes
- Signing can't be switched off, and every release is verified in native code before it's written to disk. CodePush and hot-updater support signing too, but as an opt-in.
- Requests are signed with a per-install device key, with a timestamp and a nonce the server won't accept twice.
- The server pauses a release on its own when devices report failures, and the backend has a force-update gate for sending users to the store.
- There's no vendor service to depend on. With CodePush the choice is now an archived server you run yourself.
What you give up
- Patches. hot-updater ships binary patches. dash-ota skips unchanged files, but a changed JS bundle downloads whole.
- CDN delivery. Every blob goes through your API instead of a bucket or CDN.
- Switching deployments at runtime. CodePush's
deploymentKeyoption and hot-updater's runtime channel switch have no equivalent; the channel is compiled into the binary. - Built-in crash tooling. hot-updater has
withSentry()for source maps; dash-ota leaves source maps to you.