On Expo/EAS → EAS Update via the expo-updates client (channels + phased rollouts + rollback, and diffing makes updates dramatically smaller since SDK 55/56). Hard self-host requirement → hot-updater, or the open expo-updates protocol with your own server. Anything still pointed at the retired CodePush service is silently broken — migrate.
Expo app → EAS Update (first-party; channels/rollouts/rollback built in)
self-hosted / no-vendor requirement → hot-updater, or the expo-updates protocol behind your own server — XPREM is the ready-made one (keeps the Expo client, adds rollouts/rollback/A-B on your bucket)
self-hosted AND you want a dashboard + CI integration rather than a protocol to operate → Codemagic Patch, with the 0.1.x caveat (a local Docker Compose evaluation stack makes the trial cheap)
huge fleet where full-bundle pushes cost real money (a 20MB Hermes bundle × 1M installs ≈ $1,700/push at CloudFront list) → delta patching: EAS bsdiff diffing on Expo, or Zepto's Delta self-hosted
legacy CodePush app → standalone code-push-server keeps it alive while you migrate; the hosted service is gone
the change touches native code → OTA cannot ship it; that's a store release (see RB-E-RN-VERSIONS)
RN embedded in a native app (brownfield) → Expo Updates works in isolated brownfield since SDK 55 (see RB-E-BROWNFIELD)
Options & tradeoffs
the field considered — and why each one isn’t the default here
the Expo Updates protocol is open — self-host the server, keep the battle-tested client; more ops work than EAS
XPREM (ex expo-open-ota)
the concrete server for the row above: a Go implementation of the Expo Updates protocol — multi-app, progressive rollouts + instant rollback, A/B branch splitting, S3/GCS/Azure/R2 backends, no Expo account or token. Stateless (bucket-only) or PostgreSQL control-plane mode. Core is open source; RBAC/SSO/protected branches are commercial. RENAMED from expo-open-ota at v3 — old name still all over the ecosystem
hot-updater
self-hosted open-source OTA (gronxb) explicitly positioned as the CodePush successor; actively maintained (v0.35.x, 2026-07); younger than expo-updates
25k/wk
Codemagic Patch (@codemagic/react-native-patch)
the CodePush-lineage rewrite: Codemagic ran a CodePush fork for ~18 months after Microsoft retired the service, then rebuilt it as a self-hosted OTA service — server + release worker, RN client SDK with an Expo config plugin, a `cmpatch` CLI, a web dashboard, and a one-command Docker Compose stack you can evaluate locally (Postgres + MinIO + dashboard) before provisioning anything. Positioned on CDN-first delivery, binary diffs and native fingerprinting. The differentiator vs the other self-host rows is operability (dashboard + CI vendor behind it), not protocol. VERY EARLY — npm 0.1.6, first published 2026-07-29 (verified vs the registry 2026-08-18); evaluate, do not standardize
code-push-server (standalone)
Microsoft's open-sourced server, compatible with the legacy react-native-code-push client — a self-host stopgap for existing CodePush apps, not a bet for new ones
Delta (Zepto, OSS)
self-hosted BINARY DELTA patching at quick-commerce scale — delta-cli computes bsdiff patches between releases to your S3; delta-server (Lambdas behind CloudFront) picks the patch per device (appId/jsVersion/bundleVersion manifest); device applies bspatch, swaps on next launch. Production receipts (Zepto's own): 80% adoption in 24h, 15M update requests/day, P90 patch download 305ms. GitHub-install (not yet on npm) — WATCH for a published package
react-native-code-push (RETIRED service)
App Center + the hosted CodePush service shut down 2025-03-31; the client npm has been dormant since 2024-12 — migrate
version and deprecation lines this decision tracks — for YOUR repo's sequenced plan: the doctor or npx -y @heart-it/react-brain migrate .
react-native-code-push → hot-updater (self-hosted successor) or expo-updates + EAS Update [dead · effort M]
the hosted CodePush service shut down 2025-03-31 and the client npm has been dormant since 2024-12 — anything still pointed at the service is silently broken; standalone code-push-server is a stopgap, not a destination
npm weekly downloads (from the corpus's last signals run): expo-updates 2.8M · react-native-code-push 19k · hot-updater 25k
Verified notes
NEW entry (2026-07-09): OTA delivery was a corpus gap surfaced by Native Weekly's 2026 run (bundle-diffing, runtime channel-switching, and iteration-speed coverage across issues 12–14). DURABLE FACTS, verified: Microsoft retired App Center AND the hosted CodePush service on 2025-03-31 (repo archived; standalone code-push-server open-sourced as the escape hatch); EAS Update gained binary bsdiff patches for Hermes bytecode in SDK 55 — the client applies diffs against previously installed bytecode instead of re-downloading whole bundles, an estimated ~75% download reduction, opt-in in SDK 55 and default in SDK 56 (verified vs the SDK 55 changelog). Store policy allows updating interpreted JS/assets within guidelines; native changes always require a store release. OTA is also the rollback story: republishing a known-good update is the fastest production mitigation RN has. DELTA LANE (2026-07-17, RN Rewind #49; repo verified): Zepto open-sourced Delta — the self-hosted binary-delta answer to full-bundle economics (their motivation math: one-character fix × 1M installs = ~20TB ≈ $1,700 CloudFront). Numbers are Zepto's own production telemetry; repo-install only for now — add a detect row + consider a tripwire when it lands on npm.
Canonical reading
Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.
The operational canon for this entry: staged percentage rollouts as exposure control, what to monitor while an update bakes, and aborts vs republish-rollbacks as distinct mitigations — 'sometimes it helps to go a little slower.' Vendor-authored but the staged-rollout discipline is platform-agnostic. (Body verified via the page's embedded content JSON — the blog is a JS shell.)
The reference for the hosted-default choice: how updates map to channels, phased rollouts to a percentage of users, and rollback-by-republish ('much like a new commit'). Read before designing a release-channel scheme.
Watching
Curated talks, podcasts & videos — the A/V companion to the reading list.
The scale half of this entry's delta-patching lane: why 'instant' OTA is an illusion at fleet size — full-bundle economics, adoption curves, and delivery latency — the problem statement Zepto's Delta and EAS bsdiff diffing both answer. (Verified via YouTube oEmbed: title + author.)
The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.
About over-the-air (OTA) JS updates & release channels (React Native)
Diataxis: Explanation. This page builds understanding of OTA delivery — the reasoning
behind the picks. It is not a setup guide. Store releases and native versioning are owned by
RB-E-RN-VERSIONS; brownfield constraints by RB-E-BROWNFIELD; the monitoring an update bake
relies on by RB-E-OBSERVABILITY. Read this for the why.
The one principle that organises everything: OTA is your rollback story, not a convenience
The seductive framing of OTA is "ship JS fixes without waiting for the store." The correct
framing is the inverse: republishing a known-good update is the fastest production mitigation
React Native has, and everything about how you run OTA should serve that. So run updates like
releases — channels, staged percentage rollouts as exposure control, a monitored bake period,
and abort vs republish-rollback as distinct mitigations learned before you need one. Respect
the hard boundary: OTA can update interpreted JS and assets within store guidelines, but a
native change can never ship OTA — that is a store release (RB-E-RN-VERSIONS). And know the
history: the hosted CodePush service died 2025-03-31, so "still pointed at CodePush" is the
silent failure mode of this category.
The default, and why
On Expo/EAS → EAS Update via the expo-updates client (channels + phased rollouts +
rollback, and diffing makes updates dramatically smaller since SDK 55/56). Hard self-host
requirement → hot-updater, or the open expo-updates protocol with your own server.
Anything still pointed at the retired CodePush service is silently broken — migrate.
EAS Update is the hosted default because the release-engineering pieces are built in: updates
map to channels, roll out phased to a percentage of users, and roll back by republishing
— "much like a new commit." The client got materially better in SDK 55: bsdiff diffing
applies patches against previously installed Hermes bytecode instead of re-downloading whole
bundles — an estimated ~75% smaller update downloads, opt-in in SDK 55 and default in
SDK 56. The self-host escape hatches are real: the Expo Updates protocol is open (bring your
own server, keep the battle-tested client), and hot-updater exists precisely as the CodePush
successor for no-vendor shops.
The landscape, and when each piece earns its place
EAS Update (expo-updates client) — the hosted default. Channels, phased rollouts,
republish-to-rollback; SDK 55 added bsdiff diffing (est. ~75% smaller downloads; default in
SDK 56). The first-party pick for any Expo app.
expo-updates + custom server — the protocol is open, so you can self-host the server and
keep the battle-tested client. More ops work than EAS; earns its place under a no-vendor rule.
hot-updater — self-hosted open-source OTA (gronxb), explicitly positioned as the CodePush
successor. Actively maintained (v0.35.x, 2026-07) but younger than expo-updates — the self-host
pick when you don't want the Expo client at all.
code-push-server (standalone) — Microsoft's open-sourced server, compatible with the legacy
react-native-code-push client. A self-host stopgap that keeps an existing CodePush app alive
while you migrate — not a bet for new ones.
react-native-code-push (RETIRED service) — App Center and the hosted CodePush service shut
down 2025-03-31; the repo is archived and the client npm has been dormant since 2024-12.
Migrate (urgency: dead) to hot-updater or expo-updates + EAS Update.
Tradeoffs and failure modes to name out loud
Still pointed at the dead service. The hosted CodePush shutdown doesn't crash your app —
it just stops updating it. Anything still targeting the retired service is silently broken;
standalone code-push-server is a stopgap, not a destination.
OTA run as a hotfix cannon, not a release. The production playbook is staged percentage
rollouts and a monitored bake period — "sometimes it helps to go a little slower." Shipping to
100% instantly forfeits the exposure control that makes OTA safe.
Conflating abort with rollback. They are distinct mitigations — halting an in-progress
rollout vs republishing a known-good update — and the time to learn the difference is before
the incident, not during it.
Expecting OTA to carry a native change. Store policy allows updating interpreted
JS/assets within guidelines; native changes always require a store release
(RB-E-RN-VERSIONS). Every OTA pipeline needs the "does this touch native?" gate.
Self-hosting as identity, not requirement. Both self-host paths cost real ops work, and
hot-updater is younger than expo-updates. Take them for a hard no-vendor requirement.
How it interacts with the rest of the stack
Native versioning (RB-E-RN-VERSIONS). The hard boundary: the moment a change touches
native code, OTA is out and the store-release machinery owned there is in — two halves of one
release story.
Brownfield (RB-E-BROWNFIELD). RN embedded in a native app can still get OTA — Expo
Updates works in isolated brownfield since SDK 55; the embedding constraints live there.
Observability (RB-E-OBSERVABILITY). A "monitored bake period" is only as good as the
monitoring behind it; the tooling that tells you whether to abort or roll back is owned there.
In one paragraph
OTA is your rollback story: republishing a known-good update is the fastest production
mitigation React Native has, so run updates like releases — channels, staged percentage
rollouts, a monitored bake period, and abort vs republish-rollback as distinct mitigations.
Default to EAS Update via the expo-updates client (SDK 55's bsdiff diffing, an estimated
~75% smaller downloads, default in SDK 56); with a hard self-host requirement take
hot-updater or the open expo-updates protocol on your own server. Native changes never ship
OTA — that's a store release (RB-E-RN-VERSIONS) — and the hosted CodePush service died
2025-03-31, so anything still pointed at it is silently broken and must migrate.
See also: RB-E-RN-VERSIONS (store releases; the native-change boundary), RB-E-BROWNFIELD
(Expo Updates in isolated brownfield since SDK 55), RB-E-OBSERVABILITY (the monitoring a bake
period depends on).