Expo: when you can ship an OTA update and when you cannot
Getting this wrong costs you a review cycle, or worse, ships an update that silently does nothing. The rule itself is simple; the edge cases are where people lose days.
The rule
An OTA update can change JavaScript, React components, styles, images bundled with your JS, and most application logic. It cannot change anything native.
Native means: a new native dependency, a change to any config plugin, a new permission, an app icon or splash screen, an Expo SDK bump, entitlements, or anything else that lands in the generated ios/ and android/ directories.
The practical test: did anything under plugins in app.config.ts change, or did you add a package with native code? Then it is a build.
The trap: runtime versions
An update only reaches builds whose runtime version matches. With the appVersion policy, bumping version in app.config.ts creates a new runtime version — which means your update will not reach anyone running the previous build.
This is why an OTA update sometimes appears to do nothing. It shipped correctly; it just has no matching installs.
What Apple allows
OTA updates are permitted — this is explicitly allowed for interpreted code — as long as the update does not materially change the app's purpose or add features that would alter its rating or review outcome.
Shipping a bug fix is fine. Using OTA to unlock a feature you deliberately hid from review is not, and it is the fastest way to lose your developer account.