Managing JavaScript Updates in Expo Without App Store Resubmissions
Learn how to use expo-updates to deploy JavaScript fixes to your users instantly, bypassing app store review cycles while managing native runtime compatibility.
01 Dec 2025, 12:00 UTC

The App Store Bottleneck
Waiting for app store review cycles to fix a critical typo or a logic bug in a React Native app is a common friction point. When a bug is contained entirely within the JavaScript layer—meaning no new native modules were added—you don't actually need to submit a new binary to Apple or Google. The solution is Over-the-Air (OTA) updates via expo-updates.
The core takeaway is that OTA updates allow you to swap the JavaScript bundle and assets on the user's device instantly, provided the underlying native runtime (the "shell") remains compatible.
How OTA Updates Function
In an Expo managed workflow, your app consists of two parts: the native binary (containing the React Native engine and native libraries) and the JavaScript bundle (your actual application logic). expo-updates configures the app to check a remote server for a newer version of that bundle upon launch.
When a user opens the app, the runtime checks the configured release channel. If a newer bundle is available, it is downloaded in the background. Depending on your configuration, the app will either apply the update on the next cold start or prompt the user to restart.
Configuring the Update Pipeline
To implement this, you first need the library installed in your project:
npx expo install expo-updates
Next, you must define your update configuration in app.json. This tells the app where to look for updates and which environment (channel) it belongs to.
{
"expo": {
"updates": {
"url": "https://u.expo.dev/[your-project-id]",
"enabled": true,
"checkAutomatically": "ON_LOAD"
},
"runtimeVersion": "1.0.0"
}
}
Note on Runtime Versions: The runtimeVersion is critical. If you change a native dependency (like adding a camera library), you must increment this version. If the OTA update has a different runtime version than the installed binary, the app will ignore the update to prevent a crash caused by missing native code.
Worked Example: Deploying a Hotfix
Imagine you have a production app and discover a bug in the checkout logic. You have corrected the code locally and verified it in your development build.
- Publish the fix: Run the publish command targeting your production channel from your terminal. You will need permissions to access the Expo project associated with the account.
expo publish --release-channel production - Verification: Open the app on a physical device. Because
checkAutomaticallyis set toON_LOAD, the app will fetch the manifest. The update downloads in the background; restart the app to see the fix applied. - Monitoring: Use the Expo Dashboard to confirm the update was successfully pushed and is being served to clients.
The Rollback Strategy
If a pushed update introduces a new regression, you can revert the JavaScript state without waiting for a new build. You can trigger a rollback to a previous known-good state using the --rollback flag:
expo publish --release-channel production --rollback
This instructs the server to serve the previous asset ID as the current head for that channel. Risk: Be aware that older asset IDs may be garbage-collected by the hosting provider after a certain period (typically 30 days), meaning very old versions may not be available for immediate rollback.
Critical Limitations
| Scenario | OTA Update Possible? | Required Action |
|---|---|---|
| Changing a React component's color | Yes | expo publish |
Updating expo-camera version |
No | EAS Build & App Store Submit |
Modifying app.json native config |
No | EAS Build & App Store Submit |
The most common failure point is attempting to push an OTA update that requires a native module the user doesn't have installed. This results in a "null is not an object" error when the JS tries to call a native function that doesn't exist in the binary.
Practical Verification
To ensure your OTA flow is working before hitting production:
- Create a
stagingchannel inapp.json. - Build a development binary using
eas build --profile development. - Publish a minor change to the staging channel.
- Disable your device's internet connection and launch the app to verify it falls back to the bundled JS version without crashing.
- Re-enable internet and launch again to verify the update is fetched.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.