What Are OTA Updates?
Over-the-air (OTA) updates let you push new JavaScript code and assets to users' devices without submitting a new binary to the App Store or Google Play. The app downloads the update in the background, and users see the new version on their next launch.
This is possible because React Native (and Expo) apps separate the native binary from the JavaScript bundle. The native shell stays the same while the JavaScript layer can be swapped out. Native code changes still require a full store submission.
Why OTA Updates Matter
- Speed: Skip the 1-3 day App Store review process for critical bug fixes
- Frequency: Ship updates daily or even multiple times per day
- Rollback: Instantly revert a bad update without waiting for store review
- User experience: Fixes reach users in hours, not days
The limitation is clear: OTA updates can only change JavaScript, TypeScript, and bundled assets (images, fonts, JSON). Any change to native code (new native module, permissions change, SDK update) requires a full binary release.
EAS Update (Expo)
EAS Update is Expo's official OTA update service and the recommended solution for Expo projects.
How It Works
- You run eas update from your project
- Expo bundles your JavaScript and assets, then uploads them to Expo's CDN
- Your app checks for updates on launch (or on a custom schedule)
- If a new update is available, it downloads in the background
- The update is applied on the next app launch
Key Concepts
- Runtime version: A string that identifies which native binary version an update is compatible with. Updates are only delivered to apps with a matching runtime version.
- Channel: A named deployment target (e.g., "production", "staging", "preview"). Each channel points to a specific branch of updates.
- Branch: A sequence of updates. You can think of it like a Git branch for your deployed code.
Update Strategies
- Automatic on launch: The default. The app checks for updates when it starts.
- Background download: Download the update while the user is using the app, apply on next launch.
- Forced update: Show a loading screen while the update downloads and applies immediately. Use sparingly since it blocks the user.
- Manual check: Only check for updates when the user taps a button or at specific points in your app flow.
CodePush (App Center)
Microsoft's CodePush was the original OTA update solution for React Native. However, Microsoft announced the retirement of App Center (including CodePush) in March 2025, with the service shutting down by the end of 2025.
If you are still on CodePush, you should migrate to EAS Update (for Expo projects) or consider alternatives like:
- Shorebird (for Flutter)
- Hot Updater (community-maintained CodePush alternative for bare React Native)
- Self-hosted solutions using custom update servers
App Store Rules
Both Apple and Google allow OTA updates with important restrictions:
Apple (Section 3.3.2)
Apps may download and execute code as long as the downloaded code:
- Does not change the primary purpose of the app
- Does not create a store or storefront for other code
- Does not bypass App Review for features that would normally require review
In practice, JavaScript bundle updates for bug fixes, UI changes, and minor features are fine. Adding entirely new app capabilities that should go through review is not allowed.
Google Play
Google's policies are similar. You may update interpreted code (JavaScript, Lua, etc.) as long as it complies with the Developer Program Policies. You cannot use dynamic code loading to bypass policy enforcement.
Best Practices
- Always match runtime versions: Never send a JS update that references native APIs not present in the binary. This causes crashes.
- Test before publishing: Run the update locally before pushing to production.
- Use channels: Maintain separate channels for production, staging, and development.
- Monitor after release: Watch crash reports and analytics after pushing an OTA update.
- Have a rollback plan: Know how to revert to the previous update instantly.
- Communicate updates: Consider showing a subtle indicator that a new version was applied.
OTA vs. Full Release Decision
Use OTA when:
- Fixing a JavaScript bug
- Updating text, images, or styling
- Adding a new screen built entirely in JS
- Tweaking business logic
Use a full store release when:
- Adding a new native module or SDK
- Changing app permissions
- Updating the minimum OS version
- Modifying native project configuration