Mobile — Expo Development

Over-the-Air Updates with Expo: Risks and Best Practices

Direct answer

EAS Update pushes JavaScript and asset changes straight to installed apps, skipping store review — but it can never change native code, and the biggest production risk is shipping JS that assumes native modules the installed binary doesn't have. The discipline that makes OTA safe: fingerprint-based runtime versions so incompatible updates are impossible, separate channels per environment, verifying every update on a preview channel before promoting it, and rehearsed rollbacks.

Over-the-air updates are Expo's most seductive feature and the one I see misused most often. Used well, they turn a critical bug into a minutes-long fix; used carelessly, they brick your production app in ways store review would have caught. This is the operating discipline I put in place on client projects.

Key facts, with sources

  • 71% of State of React Native 2024 respondents reported using Expo's EAS Build, ahead of manual builds with Xcode (59.7%) and Android Studio (54.5%). (InfoQ)
  • Expo SDK 52, released November 12, 2024, enabled the New Architecture by default for all new projects, and Expo Go for SDK 52 and higher supports only the New Architecture. (Expo Changelog)
  • The official React Native documentation now recommends starting new apps with a framework, naming Expo, rather than initializing a bare project. (React Native documentation)
  • EAS build caching can speed up subsequent Android and iOS builds by up to 30% and is available to all users at no additional cost as of the SDK 57 cycle. (Expo Changelog)
  • Doctolib runs Expo tooling without EAS to scale developer experience on a healthcare app serving 90 million users, showing Expo's open-source tools work independently of its paid cloud services. (Doctolib Engineering (Medium))

What OTA can and cannot change

An EAS Update delivers a new JavaScript bundle and assets — images, fonts, translations — to binaries already on users' devices. It cannot change anything native: no new native modules, no permission changes, no app icon or splash updates, no SDK upgrades. The mental model I give teams: if the change would require running prebuild again, it needs a store build.

The dangerous failures live at the boundary. You add a library with native code, it works in your development build, you publish an OTA update, and every production user crashes on launch because their binary lacks the native half. This exact incident shows up repeatedly in rescue projects I take on. Everything else in this post is essentially machinery to make that mistake impossible.

Runtime versions: use the fingerprint policy

Runtime versions are the compatibility contract: an update only installs on binaries with a matching runtime version. You can manage this by hand, but humans forget to bump versions after native changes — which recreates the crash scenario above. The fingerprint policy removes the human: Expo hashes the native-relevant parts of your project (dependencies with native code, config affecting prebuild) and derives the runtime version from it. Change anything native and the fingerprint changes, so old binaries simply never receive the incompatible update.

This is the single highest-leverage OTA setting. Every project I set up uses it unless there's a specific reason not to.

app.json — fingerprint runtime version policy
{
  "expo": {
    "runtimeVersion": { "policy": "fingerprint" },
    "updates": {
      "checkAutomatically": "ON_LOAD"
    }
  }
}

Channel discipline: preview before production

Channels bind builds to update streams: a binary built with channel preview only sees updates published to preview. My standard flow is two channels minimum. Every update is published to preview first, verified on real devices by whoever owns QA, then published to production. The verification step is non-negotiable — OTA bypasses store review, which means it also bypasses the accidental safety net review provided.

The eas update:rollback command is the emergency brake: it re-points a channel at the previous update (or the bundle embedded in the binary), and clients pick it up on their next check. Rehearse this before you need it — the first time your team runs a rollback should not be during an incident.

Publish, promote, and roll back updates
# Publish to preview and verify on device first
eas update --channel preview --message "Fix checkout crash"

# Promote the verified fix to production
eas update --channel production --message "Fix checkout crash"

# Emergency brake: re-point production at the previous update
eas update:rollback

Store policy: the line you must not cross

Both app stores explicitly permit JavaScript-level updates for React Native apps — this is settled, and OTA itself is not a policy violation. What violates policy is using updates to materially change what the app is: unlocking features that weren't in the reviewed build, changing the app's purpose, or routing around review for things reviewers care about, like payment flows.

My rule for clients: OTA is for fixes, copy changes, tuning, and incremental iterations of already-reviewed features. Anything a reviewer would want to see — new purchase flows, new permission-triggering features, significant new surfaces — goes through a store build even though OTA could technically deliver it. Losing a developer account over an update shortcut is a catastrophically bad trade for any startup.

Operational habits that keep OTA boring

A few habits separate teams that run OTA safely from teams that get burned. Write meaningful update messages — during an incident, an update history full of 'fix' is useless. Tag your error tracking with the update ID so crash reports map to the exact bundle that caused them; without this, debugging production OTA issues is guesswork. Understand your download timing: by default an update fetched during one session typically applies on the next launch, so don't expect instant fleet-wide cutover, and don't panic when metrics shift gradually.

Finally, resist update frequency creep. OTA makes shipping so cheap that teams start pushing multiple updates daily, which erodes QA discipline and makes regressions hard to attribute. I aim for deliberate, batched updates with a verification pass — the speed is there for emergencies, not as a substitute for a release process.

When to hire senior help

Senior help pays off when deciding between managed and prebuild workflows, setting up EAS-based CI/CD and OTA update channels, or untangling a project that was ejected prematurely. An experienced Expo engineer can usually configure build, submit, and update pipelines in days, a task that costs first-time teams weeks of trial and error. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships Mobile — Expo Development projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Ejecting to the bare workflow at the first native requirement instead of using config plugins and development builds, permanently giving up managed upgrades
  • Testing only in Expo Go and misconfiguring runtime versions for expo-updates, so an OTA update ships JavaScript that crashes against mismatched native binaries
  • Burning EAS build minutes on every commit in CI without caching or local builds, turning a convenience service into a surprise line item
  • Skipping several SDK upgrades in a row, then being forced into a large breaking migration when app store target API level deadlines arrive

Frequently asked questions

Are Expo over-the-air updates allowed by the App Store and Google Play?

Yes — both stores permit updating the JavaScript layer of React Native apps, and EAS Update operates within that allowance. What's prohibited is using OTA to materially change the app's purpose or ship significant features that bypass review. Keep OTA for bug fixes and iterations of reviewed functionality, and send genuinely new capabilities through a normal store release.

Why did my Expo OTA update crash the production app?

The most common cause is a native mismatch: the JS bundle references a native module that production binaries don't contain, usually because a library with native code was added without a new store build. The fingerprint runtime version policy prevents this class of crash entirely by ensuring updates only reach binaries with compatible native code. If it happens, run eas update:rollback immediately.

How quickly do users receive an EAS Update?

Not instantly. Apps typically check for updates on launch, download in the background, and apply the new bundle on the next cold start. That means fleet rollout happens over hours to days depending on how often users open the app. For true emergencies, roll back the channel first — new sessions get the fix — and treat full propagation as gradual.

Should we use Expo or plain React Native for a new app?

The React Native docs themselves now recommend starting with a framework, and Expo is the primary one; 71% of surveyed developers already build with EAS. Expo today supports custom native code through development builds and config plugins, so the old limitations that forced teams to avoid it mostly no longer apply.

Does Expo lock us into their platform?

No; expo prebuild can generate standard native iOS and Android projects at any time, and the open-source tooling works without Expo's paid EAS services, as demonstrated by Doctolib running Expo without EAS on an app with 90 million users. EAS Build, Submit, and Updates are optional conveniences, not requirements.

Is Expo production-ready for a serious commercial app?

Yes; Expo Go is only a development sandbox, while production apps ship as normal store binaries built with EAS or locally. Plan for the SDK release cadence of roughly three versions per year, since staying current is required to keep up with store policy and React Native releases.

Bottom line: Dhairya Senjaliya ships Mobile — Expo Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership