Quick answer
Fixing a production bug without waiting for App Store review means using one of three tools: an expedited review from Apple, a feature flag or remote config that disables the broken path, or an over-the-air JavaScript update if your app already supports one. Only the over-the-air update changes code, and only JavaScript and assets, within App Store Review Guideline 2.5.2. Native fixes still need a store release.
What happens while your fix waits for review?
It is Thursday afternoon. Support has forwarded the same crash report four times. You can reproduce it: a null check missing in the checkout flow, introduced in the release that went live on Tuesday. The fix is one line. You have already merged it.
Now you look at App Store Connect. The build is uploaded and sits in "Waiting for Review". On its App Review page, Apple says that on average 90% of submissions are reviewed in under 24 hours. That number is honest, and it does not help you. You are planning around the other 10%, the chance of a rejection, and the hours after approval while users get around to updating. On Android the Play Console review is a separate clock. Meanwhile the crash is still happening.
Below are the three things you can do about it, in the order most teams try them, with what each can and cannot change.
Option 1: Ask Apple for an expedited review
Apple lets you request an expedited review "if you face extenuating circumstances, such as fixing a critical bug in your app or releasing your app to coincide with an event you're directly associated with," according to the same App Review page. For a bug fix, Apple asks that the request "include the steps to reproduce the bug on the current version of your app".
What it does. It moves your submission up the queue. It does not skip review. The build still goes through the same checks, and it can still be rejected.
What it does not do. It does not get the fix onto phones. After approval you still depend on users installing the update. If you use phased release, only 1% of users get the new version on day one. You can release to everyone at once, and for a critical fix you probably should.
How often you can ask. Apple's public page does not publish a limit. The request form itself is behind a developer sign-in. In practice, treat it as something you use a few times a year, and make every request specific: the version affected, the reproduction steps, and the number of users hit. A vague request is easy to decline.
Google Play has no equivalent expedited-review form, and the Play Console help pages do not publish a target review time.
Option 2: Turn the broken path off
If the bug lives behind a feature flag, or the affected screen reads its configuration from a server, you can stop the bleeding without shipping any code at all.
Kill switch. A boolean your app fetches at launch (or on foreground) that hides a feature, disables a button, or routes users to a fallback. Firebase Remote Config, LaunchDarkly, Statsig and a JSON file on your own CDN all work. The only requirement is that the code path you want to disable is already checking the flag.
Remote config for data-shaped bugs. A wrong price or a broken URL, for example. If the value comes from a config endpoint, fix it there.
Limits. A kill switch removes the feature. It does not repair it. Users of your checkout flow now have no checkout flow, which may be worse than the crash depending on how many users hit the crash. And it only works for code that was already written to check a flag. You cannot add a flag to shipped native code from the outside.
The lesson most teams take from this is structural: wrap anything new and risky in a flag before it ships, so that this option exists when you need it.
Option 3: Ship the fix as an over-the-air JavaScript update
If your app is built with React Native (or another framework that runs interpreted code), the app binary contains two layers. A native layer, compiled and reviewed by the store, and a JavaScript bundle that the native layer loads at launch. An over-the-air (OTA) update replaces that bundle with a new one, downloaded from a server you control. The native layer does not change.
What it can change. Anything in JavaScript: the null check in checkout, a broken layout, copy, translations, a logic bug in a reducer, and static assets such as images. The EAS Update introduction sums it up as fixing "a bug or crash in JavaScript code" and updating "copy, translations, UI styling, or screen layouts".
What it cannot change. Native code and native dependencies, app permissions, the SDK version, or anything else that lives in the compiled binary. Those still require a new build, a store submission, and review. The same page lists these as cases for a new binary, and every OTA system has the same limit.
Store policy. An OTA bug fix is not a loophole around review. App Store Review Guideline 2.5.2 says apps "may not download, install, or execute code which introduces or changes features or functionality of the app", and the Apple Developer Program License Agreement, section 3.3.2, permits interpreted code to be downloaded so long as it "does not change the primary purpose" of the app. The current agreement sits behind a developer sign-in, so the link here is to a publicly filed copy of the DPLA.
Google Play's Device and Network Abuse policy restricts downloading executable code but states the restriction "does not apply to code that runs in a virtual machine or an interpreter". A bug fix to your JavaScript sits inside those rules. A new feature that changes what the app is for does not, and you should submit that through the store.
What you need in place. An OTA update only reaches builds that already contain the update client. If your production app does not have one today, this option is not available for the current bug. You get it for the next one, once you ship a build that includes the client.
Time to reach users. The update is available as soon as you publish it. Users get it when their app next checks, which by default is on the next cold launch. There is no install step and no store listing to open.
Expedited review, kill switch or OTA update: which should you use?
| Expedited store review | Kill switch or remote config | OTA JavaScript update | |
|---|---|---|---|
| Time until the fix is available | Hours to days, at Apple's discretion. Then user installs | Minutes | Minutes |
| Time until users are running it | After they install the new version | Next config fetch, often on next launch | Next update check, by default next cold launch |
| What it can change | Anything, native and JavaScript | Only paths already behind a flag; disables, does not repair | JavaScript and assets only. Not native code, permissions or SDK version |
| Risk | Rejection resets the clock. Phased release slows adoption | Feature is gone until a real fix ships | A bad update needs a rollback path. Native mismatch if runtime version is wrong |
| Requirements | A submitted build and a written reproduction | Flag checks already in shipped code | Update client already in the shipped build |
| Best for | Native bugs. Anything the other two cannot touch | Fast containment of a risky feature | Repairing JavaScript logic without a store cycle |
Most incidents use two of these. Flip the kill switch to stop the damage, ship the OTA update to repair it, and let the store release follow at its normal pace so the fix is also baked into the next binary.
How do you ship an OTA fix without causing a second incident?
An OTA update reaches users at the speed of a deploy, which is also the speed at which a mistake reaches users. Three habits keep a fast fix from turning into a second incident.
Step 1: Test the fix on a build that matches production
Publish the fix to a staging channel first and run it on a build that has the same native code as the production app. Expo's guide to deploying updates recommends creating a staging build every time you create a production build, so that you always have a test target with an identical runtime. If your fix passes there, promote the same bundle to production rather than building a new one.
Step 2: Roll it out to a percentage of users first
Send the update to 5% or 10% of users. Watch crash rate and the metric the bug was breaking (checkout completions, in this example) for long enough to see real traffic. Then widen. Apple's phased release and Google Play's staged rollout work the same way for binaries; here you apply the idea to the JavaScript layer. The difference is that you can move from 10% to 100% in an hour rather than a week.
Step 3: Know your rollback before you publish
Two rollback shapes exist. Republish the last known-good update on top of the bad one, so that clients pick it up on their next check. Or tell clients to fall back to the bundle embedded in the binary. Decide which one you would use before you ship. Also check whether the update changed persisted state (local storage, on-device database schemas). If it did, rolling back may not be safe and you will need to fix forward, as Expo's error recovery guide explains.
The native binary that goes to the store next should include the same fix in its embedded bundle. Otherwise a user who installs the store update will briefly run the old code again until the next OTA check.
Where Expo fits
EAS Update is Expo's hosted service for delivering JavaScript bundle and asset updates to apps that include the expo-updates library. It works with Expo projects and with existing React Native projects that add the library. It updates the JavaScript layer only; native changes still go through EAS Build and a store release.
Publishing a fix to production is one command:
To roll the fix out gradually, add a percentage and raise it later:
Only one update can be rolled out on a branch at a time. The rollouts guide notes that you must end the rollout (set it to 100, or revert it) before you can publish another update for the same runtime version.
If the fix makes things worse, you have two rollback paths:
The eas update:rollback command walks you through choosing between a previously published update and the embedded update, as described in the rollbacks guide. The eas update:republish command places a previous update on top of the branch, like a Git revert, per the EAS CLI reference. If you verified the fix on a staging channel and staging uses the same environment variables and signing as production, you can promote the exact tested bundle with eas update:republish --destination-channel production instead of publishing a new one, as the deployment guide shows.
Limitations
EAS Update cannot change native code, native dependencies, permissions or the Expo SDK version; those need a new binary. The update only reaches builds with a matching runtime version, which is how it avoids sending JavaScript to a native layer that cannot run it. By default the app checks for an update on cold launch, so adoption follows your users' launch pattern unless you use the Updates API to check in the foreground or background. There is no built-in "mandatory update" flag; you build that with the API if you need it.
On Expo's pricing page, the free plan includes 1,000 monthly active update users, Starter includes 3,000, Production 50,000, and Enterprise 1,000,000, with usage-based pricing after that. End-to-end code signing is available on the Production and Enterprise plans.
For the longer version of the safe-rollout process above, see The production playbook for OTA updates on the Expo blog. If you are moving from CodePush, which Microsoft shut down with App Center on 31 March 2025, the CodePush migration guide covers the differences.
Production bug fixes at a glance
| Question | Answer | Source |
|---|---|---|
| How fast does Apple review? | 90% of submissions in under 24 hours, on average | Apple |
| What qualifies for expedited review? | "Extenuating circumstances", such as a critical bug fix or an event tied to your app | Apple |
| Phased release, day one | 1% of users | Apple |
| What can an OTA update change? | JavaScript and assets only | Expo docs |
| Store rule for downloaded code | Apple 2.5.2 and DPLA 3.3.2; Google Play interpreter exception | Apple, Google |
| EAS Update free tier | 1,000 monthly active users | Expo pricing |
Next step
Before the next incident, make sure a shipped build can receive a JavaScript update and that you know your rollback command. For Expo and React Native apps, Get started with EAS Update covers install, configuration and the first publish.
Verified on 12 September 2026.
