Over the air updates carry your JavaScript in minutes, and they stop where the native code begins.
An over the air update replaces the non native parts of an installed React Native app: the JavaScript bundle, the styling and the bundled images. It cannot touch native code, native dependencies, app permissions or the Expo SDK version, because those were compiled into a binary a store already reviewed. The service most React Native projects use is EAS Update, which serves updates to any project carrying the expo-updates library. CodePush, the tool every older article names, was retired on 31 March 2025 and both of its repositories are archived. Every limit and date below comes from Expo's, Apple's, Google's and Microsoft's own documentation, read on 10 October 2026. If you have not shipped a binary yet, start with building with EAS.
What follows is the exact split between an update and a build, what the App Store and Google Play rules actually say about an app that updates itself, what replaced CodePush, and how to publish without breaking the installs you already have. The capability is real. The limits are where people get hurt.
See what can ship over the airThe short version
An update swaps the top half of your app and the stores reviewed the bottom half.
Expo's documentation describes a build as two layers: a native layer compiled into the binary, and an update layer that is swappable with other compatible updates. An over the air update replaces the second layer and never the first. So the question is never how big your change is. It is which layer the change lives in. A rewritten checkout screen is the update layer. One added camera permission is the native layer, and no amount of JavaScript gets it onto a phone.
Both stores allow this, and neither one reviews the update. Apple permits interpreted code to be downloaded into an app inside three written conditions. Google Play forbids an app from updating itself by any route other than Play, then exempts code that runs in an interpreter. In other words the rules are about what your update does, not about how it got there, and that is the sentence most articles about this skip.
What can you change over the air, and what needs a new build?
Expo publishes the split as a table, and it is short enough to memorise. Over the air: fix a bug or a crash in JavaScript code, update copy, translations, UI styling or screen layouts, roll out a change to a percentage of users, publish from CI, and test with an internal team before production. Needs a new binary: a change to native code or native dependencies, a change to app permissions such as camera or location, an update to the Expo SDK version, and anything else that requires a new app binary version. There is no grey band in the middle.
The mechanism that enforces this is the runtime version. Expo describes it as a property that guarantees compatibility between a build's native code and an update, and every build carries one alongside its channel and its platform. An update is only offered to builds with a matching runtime version. Forget to raise it after adding a native library and you get the failure Expo spells out: the build thinks the incoming update is compatible, loads it, calls code that is not there, and expo-updates may detect the error and roll back to the last working update. The appVersion policy ties the runtime version to your app version, the fingerprint policy raises it whenever anything that could affect the native runtime changes, and you can also set it by hand, per platform if you need to.
One timing detail changes how you plan a release. By default expo-updates checks for an update when the app is cold booted from a killed state and downloads it without blocking the launch. The person keeps using the old copy for that session and gets the new one on their next cold boot. Expo recommends against blocking startup on the download, because on a slow connection that leaves people on a splash screen long enough to give up. So the honest promise is not instant. It is two launches, for the share of your users who actually quit and reopen the app.
Expo documentation, EAS Update, read 10 October 2026
Check your change
Can this ship over the air?
Tick everything in the change you are about to ship. The verdicts follow Expo's own list of what an update can and cannot carry.
Nothing ticked yet. The short rule: if the change is in your JavaScript, your styling or your bundled images, it goes over the air. Anything else is a build.
What do the App Store and Google Play rules actually say?
Start with the sentence that frightens people. App Store Review Guideline 2.5.2 says apps should be self contained in their bundles, may not read or write data outside the designated container area, and may not download, install or execute code which introduces or changes features or functionality of the app, including other apps. Read on its own that is a flat ban on what this page describes, and it is why the argument keeps coming back.
The carve out is in a different document, which is why so few people quote it. The Apple Developer Program License Agreement, in the Executable Code part of section 3.3.1, says an application may not download or install executable code, and then that interpreted code may be downloaded to an application so long as it does three things. It does not change the primary purpose of the application by providing features or functionality inconsistent with the intended and advertised purpose of the application. It does not bypass signing, sandbox or other security features of the operating system. And, for apps on the App Store, it does not create a store or storefront for other applications. JavaScript is interpreted code. A bug fix that keeps your app doing what your product page says it does sits inside all three.
Google writes it the other way round. The Device and Network Abuse policy says an app distributed via Google Play may not modify, replace or update itself using any method other than Google Play's update mechanism, and that an app may not download executable code such as dex, JAR or .so files from a source other than Google Play. Then the exemption: that restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs. A JavaScript bundle qualifies. A downloaded native library does not, which is the same line Apple draws from the other side.
Expo puts the practical version in its own FAQ: your updates need to follow the App Store and Play Store guidelines, including the content of the updates and how you use them, and that usually means changes to your app's behaviour need to be reviewed. Treat the channel as a repair tool, not a release channel. A crash fix today and a submission that contains the same fix afterwards is the pattern Expo itself recommends, and it is the one used for hotfixing a published app. Shipping a whole unreviewed feature set this way is the thing both rules exist to stop.
Apple, App Store Review Guidelines, guideline 2.5.2, read 10 October 2026
Is CodePush still the tool to reach for?
No. Microsoft retired Visual Studio App Center on 31 March 2025, and CodePush went with it. The client repository now opens with a notice that says the repository is being archived because CodePush, along with other App Center features, was retired on that date. Analytics and Diagnostics got a reprieve and nothing else did. If you are reading a tutorial that tells you to run the CodePush CLI against a hosted service, that service does not exist.
There is a leftover, and it is worth knowing precisely what it is. Before the shutdown Microsoft published a standalone CodePush server that can be deployed and run independently of App Center, and it is compatible with the old client SDK. That repository is archived too. Microsoft states the code is provided as is, that it will not provide support services for it, and that it cannot accept contributions or issues. The license is MIT, so a fork is allowed and some exist. You would be running an unsupported update server for an unsupported client. The client also never reached the React Native New Architecture: Microsoft's own warning says you have to opt out of it to use the plugin on React Native 0.76 and later, and the New Architecture is where React Native went.
The maintained answer for a React Native or Expo project is the expo-updates library with EAS Update behind it. Expo publishes a migration guide, and the shape of the move is the part people underestimate: uninstall react-native-code-push, strip its references out of the JavaScript and the native code, configure expo-updates, then rebuild and resubmit to both stores, because you changed the update provider inside the binary. Expo also fills the gaps the CodePush CLI had flags for. There is no mandatory flag, so critical updates are something you build. Deployment switching at runtime is an override call. Rollbacks are a command. Gradual releases are rollouts. If the Android side is what you care about, the mechanics of auto-updating an Android app cover how a Play release and an over the air update sit side by side.
Expo documentation, EAS Update and the CodePush migration guide, read 10 October 2026
How do you ship one without breaking the installs you already have?
Four habits cover almost everything that goes wrong. Pick a runtime version policy and let it do the thinking, because the fingerprint policy raises the runtime version whenever something that could affect the native runtime changes, while appVersion only moves when you remember to move your app version. Keep separate channels, so your staging builds on TestFlight and the Play internal testing track get the update first and your production builds get it when you say so. Use a rollout to put the update in front of a small percentage and watch the error rate before everyone has it. And know the undo before you need it: the command is eas update:rollback, and you can also republish an earlier working update on top of a bad one, which Expo compares to a new commit rather than a rewrite of history.
The failure worth rehearsing is the compatibility one, because it lands on people who already shipped. A build goes out with runtime version 1.0.0. Later an update adds a screen that uses a newly installed native library, and nobody raises the runtime version. The old builds accept the update, load it, call native code that is not in them, and the rollback path is the only thing standing between you and a store page full of one star reviews. The guard is cheap: raise the runtime version whenever you install or change native code, and compare fingerprints between a build and an update when you are not sure what changed.
The last risk is not technical at all. Apple's three conditions and Google's policy both describe an obligation that continues for as long as the app is on the store, so an update that quietly turns on a feature your reviewed build did not have is a rule problem even when it works perfectly. Write down which changes your team is allowed to push without a submission, because the answer differs between a crash fix and a redesign, and put that line in your release routine next to everything else that belongs in app maintenance. A capability nobody has written rules for is the one that gets abused by accident.
Three routes to a fix, and what each one costs you
| What you need | CodePush | EAS Update | A new store build |
|---|---|---|---|
| Where it stands in October 2026 | Retired on 31 March 2025, both repositories archived | Current, documented and maintained by Expo | The only route for a native change |
| Carries JavaScript, styling and bundled images | Yes | Yes | Yes |
| Carries native code or native dependencies | No | No | Yes |
| Carries a new permission or an SDK bump | No | No | Yes |
| Reaches users without waiting for a review | Yes | Yes | No |
| Undoing a bad release | Only if you run the archived standalone server yourself | eas update:rollback, or republish an earlier update on top | Another build, and another review |
Where this lands if a builder generated your app
Newly is an AI app builder. You describe an app in plain English and it writes a real React Native and Expo project in TypeScript that you own. It runs the app on cloud iOS and Android simulators while it builds, uploads iOS builds to TestFlight, publishes to Google Play internal testing, and builds a standalone Android APK. It syncs two ways with GitHub. It is $25 a month and there is no free plan. It does not submit to the App Store for you, because Apple requires the owner of the content to submit the app, so the first review is yours either way.
The honest part, checked in the generated project rather than assumed: the template does not include expo-updates. There is no such dependency, no updates block in the app config and no runtime version, which means a build you ship launches the JavaScript bundle baked into it and fetches nothing at startup. Over the air updates are not a switch you flick in the product. They are something you add to a project you own.
Which is the point of owning the code. Install expo-updates, set a runtime version policy in the app config, configure a channel, and the project behaves like any other Expo project from then on. Do it through the GitHub sync so the change is in your history. Then remember the ordering nobody warns you about: adding the update client is itself a native change, so the first build after you add it has to go to the stores the ordinary way. Only the builds that carry the client can receive anything over the air.
Questions people ask about over the air updates
They are published bundles of the non native parts of your app that an installed copy downloads and runs instead of the code inside its binary. Expo describes it as an app updating its own non native pieces, such as JavaScript, styling and images. The app has to ship with an update client in it, which for React Native means the expo-updates library, and the update is only served to builds whose runtime version matches. Nothing in the native layer changes, because that layer was compiled into the binary a store already reviewed.
Describe the app, ship the binary, then keep the JavaScript loose
Get a real React Native and Expo project you own onto TestFlight and the Play internal testing track first. Over the air updates are a few lines of config on top of a project that already builds, and they are worth nothing until something of yours is installed on somebody else's phone.
Start building