OTA install vs OTA code updates: CodePush, EAS Update
· 7 min read
"OTA" gets used for two different things in mobile development, and mixing them up leads to wrong tool choices. Ask one developer about OTA and they mean installing a test build from a link. Ask another and they mean pushing a JavaScript change to apps already on users' phones without a store release. Both happen over the air. They solve different problems, run under different store rules, and need different tools.
This article separates the two, covers where CodePush went after App Center was retired, and gives a simple way to decide which one you need.
Two kinds of OTA
| OTA install | OTA code update | |
|---|---|---|
| What moves | A whole signed app (.ipa or .apk) | A JavaScript bundle, Dart patch or web assets |
| Who receives it | Testers, clients, QA | Users of an app already installed, often from a store |
| Native code changes | Yes, it is a full build | No, native code stays the same |
| Needs an SDK in the app | No | Yes, an update client checks for and applies updates |
| iOS requirement | Ad Hoc, Development or Enterprise signing | Any build that includes the update client |
| Typical tools | AppForShare, Firebase App Distribution, Diawi | Expo EAS Update, self hosted CodePush server, Shorebird, Capgo |
OTA install: getting a build onto a phone
An OTA install means a tester taps a link on the phone and the whole app installs, without going through the App Store or Google Play. On Android this is a normal APK download that the phone installs once the browser is allowed to install apps. On iOS, the link points to a small manifest file over HTTPS, and iOS downloads and installs the signed .ipa described in it.
The iOS side has a hard rule: the build must be signed for distribution outside the store. That means Ad Hoc (only devices whose UDID is in the provisioning profile), Development, or Enterprise for in-house apps. A build signed for the App Store cannot be installed from a link, and neither can a TestFlight build. If an install fails with "Unable to install", the profile is the first thing to check; see why an app will not install on iOS.
Because the whole binary changes, an OTA install can test anything: new native modules, new permissions, a new SDK version, a new minimum OS. That is its main advantage over a code update. Its limit is reach: it is for people you choose, not for your store users.
OTA code updates: changing an app that is already installed
Frameworks that run part of the app in an interpreter or a runtime can swap that part without a new binary. React Native runs a JavaScript bundle, Capacitor and Cordova run web assets, and Shorebird patches Flutter's Dart code. An update client inside the app checks a server, downloads the new bundle and uses it on the next launch.
The key limit: native code does not change. Expo's documentation describes a build as two layers, a native layer built into the binary and a swappable update layer. If an update needs a native module that the build does not contain, it cannot work. Expo uses a runtimeVersion to make sure an update only goes to builds whose native code it is compatible with. Any native change still needs a new build, and for store users, a store release.
What happened to CodePush
CodePush was the best known OTA code update service for React Native, and it was part of Visual Studio App Center. App Center was retired on March 31, 2025, and CodePush with it.
Before the shutdown, Microsoft published a standalone CodePush server that can be self hosted and works with the existing React Native CodePush client SDK. The repository was archived in May 2025 and is MIT licensed, so it is available for teams and the community to fork and run, but it is not a hosted service and Microsoft is not developing it further. Running it means you own the server, storage, scaling and security of your update pipeline.
The common paths for teams that used CodePush:
- Expo EAS Update: Expo's hosted update service, built on the
expo-updateslibrary. It works for Expo projects and for bare React Native projects that addexpo-updates. Updates are grouped into channels so preview builds and production builds can receive different updates. - Self hosted CodePush server: keeps the existing client SDK, at the cost of running the backend yourself.
- Shorebird: a code push service for Flutter apps that patches Dart code on Android and iOS.
- Capgo: live updates for Capacitor apps.
Check each service's current pricing and limits before you commit; they change, and they are usually based on how many devices download updates each month.
If you moved off App Center and only used it to get builds to testers, that is the other kind of OTA. Our App Center migration guide covers replacing the Distribute step.
What the stores allow
Code updates are allowed, within limits. These are the rules that matter, as Apple and Google publish them today.
Apple
App Review Guideline 2.5.2 says apps should be self contained in their bundles and may not download, install or execute code which introduces or changes features or functionality of the app. Guideline 4.7 separately allows HTML5 and JavaScript mini apps and mini games, with the developer responsible for that content.
The Apple Developer Program License Agreement has the clause that OTA update tools rely on: interpreted code may be downloaded to an app as long as it does not change the app's primary purpose by adding features inconsistent with its intended and advertised purpose, does not create a store or storefront for other code or apps, and does not bypass signing, the sandbox or other security features of the OS.
In practice: shipping a bug fix, copy change or small UI adjustment in JavaScript is the normal use and widely accepted. Using an update to add a new major feature that review never saw, or to change what the app is, is the kind of use the rules are written against.
Google Play
Google Play's Device and Network Abuse policy prohibits apps and SDKs from downloading executable code such as dex files or native code from a source other than Google Play. Code that runs in a virtual machine or interpreter with indirect access to Android APIs, such as JavaScript in a WebView, is not covered by that ban, but interpreted code loaded at run time must not allow violations of other Play policies.
So on both stores, a JavaScript or asset update is generally fine, and a native code change is not something you can deliver outside the store. Neither store promises approval for any particular approach, and reviewers judge how you use it. See common App Store rejection reasons for related pitfalls.
Which one do you need?
| You want to | Use |
|---|---|
| Let QA or a client try a new build before release | OTA install |
| Test a change that adds a native module, permission or SDK | OTA install |
| Test on iPhones without TestFlight review | OTA install with an Ad Hoc build |
| Fix a JavaScript bug for users on the store version today | OTA code update |
| Give testers frequent JS only changes on a preview build | Either: a preview channel, or a new build per change |
| Ship a new major feature to store users | A store release |
Many teams use both. A typical React Native setup: every merge produces a full build that testers install from a link, the release candidate goes through the store, and small JavaScript fixes after release go out as code updates to the matching runtime. The test build and the update channel serve different people.
One caution when you combine them: if your test builds also include an update client, make sure they point at a preview channel, not production. Otherwise a tester's build can quietly pick up a production update and you are no longer testing the binary you think you are.
Where AppForShare fits
AppForShare does the first kind of OTA only. You upload a signed .ipa or .apk, get an install link and QR code, and testers install the whole build from the phone's browser. There is no SDK to add, and AppForShare does not push code updates to installed apps, re-sign builds or publish to the stores.
What it adds around the install: a "what changed" view that compares each build with the previous one (version, permissions, minimum OS, SDK, frameworks, size), a store readiness check on account builds, password protected links on Free, and on Pro, release notes, CI uploads and install analytics. For React Native teams, sharing a React Native build with testers walks through the export and upload.
If you need the install side, create a free account and upload a build, or see plans from $12 a month on the pricing page. For the upload step in your pipeline, the CI docs show the one line npx command.


