A critical bug is live in production. Your fix is ready. And now your team is staring at a potential multi-day wait for app store review.
This is exactly what over-the-air (OTA) updates are designed to solve. Tools like Expo EAS Update, CodePush, Shorebird, Revopush or Stallion make it easy to push updates directly to users’ devices. But OTA updates don't bypass app store rules, they operate within boundaries that both Apple and Google have defined. Those boundaries are different on each platform, and crossing them can come with serious penalties, like getting your app removed.
This guide explains what the policies actually say and what you can do to stay compliant.
Note: This article is based on primary policy documents and documented enforcement history, as of Q2 2026. Policies can change, please review the up-to-date policies before making compliance decisions.
Why are mobile OTA updates possible at all?
OTA updates work because of how some cross-platform mobile frameworks are built. In a mobile context this is most commonly encountered with React Native, where the JS bundle is separate from the native binary, and Flutter, where tools like Shorebird enable OTA by running a modified Dart runtime.
Most cross-platform apps compile into two distinct layers:
- Native binary: compiled code that interacts with device hardware and OS APIs.
- Interpreted or scripted layer: the business logic and UI, written in a language that runs at runtime rather than being compiled into the binary.
Because the interpreted layer is separate from the binary, it can be replaced without touching the native code. That's the mechanism OTA tools use.
In a mobile context, OTA updates are typically encountered with React Native. There the JS bundle is entirely separate from the native binary and can be replaced via tools like Expo’s EAS Updates or Bitrise CodePush.
What does Apple's policy say about OTA updates?
To understand Apple's OTA restrictions you need to read two policies together.
- App Store Review Guideline 2.5.2 states that:
“Apps should… [not] download, install, or execute code which introduces or changes features or functionality of the app, including other apps.”
Read literally, this sounds like a ban on OTA. In practice, Apple applies a functional test: not whether code was pushed, but whether it changed what the app fundamentally does.
- Developer Program License Agreement (DPLA), Section 3.3(b) is more precise and more important. This is the contract every developer signs when joining the Apple Developer Program. Phillip Shoemaker, who ran Apple’s App Store Review from 2009 to 2016, has written directly about Section 3.3(b), calling it "one of the most important, misunderstood, and fiercely enforced rules Apple has ever written." It reads:
"Interpreted code may be downloaded to an Application but only so long as such code:- a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store,
- (b) does not create a store or storefront for other code or applications, and
- c) does not bypass signing, sandbox, or other security features of the OS."
So where does React Native OTA stand? JavaScript bundles are interpreted code, which means the DPLA explicitly permits them to be pushed, as long as the three conditions are met. When the two policies appear to conflict, the DPLA wins: it is the more specific provision, and specific rules override general ones. Apple's enforcement history bears this out.
The line isn't "no pushed code." It's three rules: don’t transform your app, don’t introduce a store-within-a-store, and don’t compromise users' security.
How has Apple enforced its policies in the past?
Apple's OTA rules can feel abstract until you see how they have actually been applied. Two cases are worth knowing.
In March 2017, Apple removed apps using Rollout.io, citing Guideline 2.5.2 and DPLA 3.3.2. The issue wasn't OTA updates per se, it was what Rollout was patching. Rather than replacing a JS bundle, Rollout used method swizzling to modify the Objective-C runtime directly on a live device, reaching into compiled native code and calling private APIs that had never been reviewed.
CodePush was unaffected. As Expo's CEO explained at the time, React Native JavaScript "will only call into a set of native calls that have been audited and reviewed at App Store submission time". The native layer never changes. The 2017 enforcement didn't threaten React Native OTA. It validated the model it was built on.
The same rule is now being applied to vibe coding apps. In early 2026, Apple blocked updates to Replit and Vibecode, and pulled Anything from the store entirely, on the basis that apps generating and executing code to modify their own behaviour at runtime are updating themselves outside of review. The context is new but the rule is not.
For React Native teams, the implication is unchanged. A fixed JS bundle calling only audited native APIs has always sat on the right side of this line.
What can you push to iOS via OTA?
What does Google Play's policy say about OTA updates?
Google Play's position on OTA is set out in a single policy, but it contains three sentences that need to be read in sequence.
The Device and Network Abuse states:
“An app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism.
Likewise, an app may not download executable code (such as dex, JAR, .so files) from a source other than Google Play.
This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser).”
The first two sentences read like a blanket ban. The third is the carve-out that changes everything.
Google's restriction targets two things: apps updating their own binary, and native executable code downloaded from outside Play. JavaScript running in a sandboxed interpreter is explicitly excluded. React Native's JS runtime only accesses Android through a bridge layer that was reviewed at submission time, and that indirection is exactly what the carve-out describes.
So where does React Native OTA stand? On firmer ground than on iOS. The VM carve-out is an explicit permission in the policy text, not an inference from enforcement history. The JS bundle is content running inside a reviewed container, not a modification to the app itself.
Interestingly, users also don’t encounter the same explicit restrictions on what the update does that you see with Apple. That said, OTA is not a policy-free channel: whatever you push must still conform with Google Play's Developer Program Policies in full.
What can you push to Android via OTA?
What are the five things you should do before each OTA release?
For peace of mind, you can run through this checklist to make sure your release does not cross any store policies:
- Native code check: run a fingerprint check. If native code changed, stop and plan a store release.
- Functional scope check (iOS): does this introduce anything that has significantly transformed the nature of your app? If yes, it belongs in a store release, not OTA.
- Payment check (both stores): does this change anything related to purchase, subscription, or entitlement? If yes, treat as requiring review.
- Policy compliance check (both stores): does the pushed code comply with all other platform policies (content, privacy, advertising)?
- Rollback check: is your rollback strategy in place and tested?
It is good practice to check relevant app store policies periodically, as they might have changed. Whenever you are in doubt it is always best to submit to the stores for review.
TL;DR: what do app stores actually allow with OTA updates?
Both Apple and Google permit OTA updates to interpreted code. Apple allows them for fixes, improvements, and content updates, but not new features or changes to core purpose. Google is more permissive: any JS-only change is permitted, provided it complies with Play policies. Neither platform allows native code via OTA.
If you're ready to set up OTA for your React Native app, getting started with Bitrise CodePush takes about ten minutes, and the free tier includes 5 GiB of storage. For a closer look at what the full release flow looks like, see shipping React Native updates with CodePush on Bitrise.

