Using Expo OTA Updates to Ship JavaScript Changes Without a Store Release
Learn how to push JavaScript‑only changes to existing Expo builds using OTA updates, with a concrete publish example, verification steps, and key limitations.
13 Nov 2025, 19:18 UTC

The problem: you need to fix a bug or tweak UI, but a store submission would take days
When you’re working on a managed Expo project, any change to the JavaScript bundle can be shipped instantly to users who already have the app installed, as long as you don’t touch native code. This avoids the waiting period associated with Google Play Store or Apple App Store reviews and lets you iterate faster.
How Expo’s over‑the‑air (OTA) update mechanism works
Expo publishes a JavaScript bundle and a manifest to its servers. At app launch, the expo-updates library checks the manifest for a newer runtimeVersion. If a newer version exists and the device has network access, the bundle is downloaded in the background. Depending on the update policy you set (e.g., UPDATE_POLICY.AUTOMATIC or UPDATE_POLICY.ON_LOAD), the app may prompt the user to reload immediately or wait until the next launch.
The update is versioned by a combination of:
- runtimeVersion – tied to the native binary (the Expo SDK version).
- platform‑specific manifest – contains the hash of the JavaScript bundle and asset metadata.
If the download fails or the device is offline, the app falls back to the last successfully downloaded bundle, guaranteeing a known‑good state.
Worked example: publishing an OTA update
- Create or open a managed Expo project
# In your terminal, run: expo init ota-demo --template blank cd ota-demo # Install the updates library if not already present expo install expo-updatesYou need read/write access to the project directory and an Expo account (free tier is sufficient).
- Make a change to the JavaScript bundle
Edit
App.jsto add a simple banner, for example:import { StatusBar } from 'expo-status-bar'; import { Text, View, StyleSheet } from 'react-native'; export default function App() { return ( , 🚀 OTA update demo – reload to see this! ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center' }, banner: { position: 'absolute', top: 0, left: 0, right: 0, backgroundColor: '#ffeb3b', padding: 8, textAlign: 'center' }, }); - Publish the update to Expo’s servers
# From the project root: expo publishThis command uploads the bundled JavaScript and assets, creates a new manifest, and returns a
publishTimeandruntimeVersion. No native rebuild is required. - Obtain the update on a device
You can test with a development client or a production build:
# Development client (requires a local Metro bundler) expo run:ios # or expo run:androidWhen the app launches, it will check for a newer manifest. If you are online, you should see the yellow banner added above. If you disable network after the first launch, close the app, and reopen it, the banner will still appear because the update was already cached.
- Verify via the Expo dashboard
Log in to expo.dev, select your project, and go to the “Updates” tab. You will see a list of published runtimes, each with a timestamp and the associated manifest version. Confirm that the manifest hash matches the bundle you just published.
Trade‑offs and limitations
- Native code cannot be changed OTA. Adding a new native module (e.g.,
expo-cameraif it wasn’t linked before) requires a new store binary. - Package size limit. The total size of the JavaScript bundle plus assets is currently capped at around 50 MB. Large asset updates may need to be split or hosted externally.
- Offline fallback. If a device has no connectivity at launch, it will run the last successfully downloaded bundle, which could be stale. Consider a version check in your own backend if real‑time data is critical.
- User experience. Depending on the update policy, users may see a prompt to reload, which can be disruptive if overused.
Actionable closing
For most UI tweaks, bug fixes, or feature flags that live purely in JavaScript, Expo’s OTA update flow is the fastest way to get changes into users’ hands. Start by publishing a small change, verify it appears on a device, and monitor the size of your bundles to stay within the 50 MB limit. When you inevitably need to touch native code, plan a store release; otherwise, let the OTA pipeline handle the iteration speed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.