iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

OTA Updates

Over-the-air (OTA) updates ship JavaScript and asset changes to installed apps without going through the App Store / Play. Expo Application Services (EAS) is the modern path on React Native — push a JS-only change, devices fetch it on next launch. Native code (any new plugin, native config) still needs a binary release.

EAS Update with channels, rollout, and runtimeVersion

EXAMPLE
# 1) Install + configure expo-updates
# npx expo install expo-updates
# Update app.config.ts
# export default {
#   ...
#   updates: { url: 'https://u.expo.dev/<project-id>' },
#   runtimeVersion: { policy: 'appVersion' },     // 1.2.3 binaries get 1.2.3 OTA bundles
#   ios:     { ... }, android: { ... },
# };

# 2) Push your first OTA
# npx eas update --branch production --message 'Fix checkout copy'
# Devices on the 'production' channel pick this up on next launch.

# 3) Channels — environment-scoped distribution
# eas.json
# {
#   "build": {
#     "production": { "channel": "production" },
#     "preview":    { "channel": "preview" },
#     "development":{ "channel": "development" }
#   }
# }
# Promote an update from preview to production WITHOUT rebuilding the binary:
# npx eas update:edit --branch preview --rollout-percentage 10
# Test on 10% of installs first, then bump.

# 4) Force-apply latest update on next launch (in JS)
import * as Updates from 'expo-updates';

async function checkForUpdates() {
  if (__DEV__) return;
  try {
    const update = await Updates.checkForUpdateAsync();
    if (update.isAvailable) {
      await Updates.fetchUpdateAsync();
      // Show a banner OR reload silently
      await Updates.reloadAsync();
    }
  } catch (err) {
    console.warn('OTA check failed', err);
  }
}
// Call at app start; debounce to once per session.

// 5) runtimeVersion — the safety net
# What it controls:
#  - The binary AND the OTA bundle agree on the native API surface
#  - An OTA bundle built for runtimeVersion '1.3.0' WILL NOT load on a binary
#    whose runtimeVersion is '1.2.0' (missing native module = crash)
#
# Policies:
#  - 'appVersion':    derived from app version (1.3.0); bump = new runtime
#  - 'sdkVersion':    one runtime per Expo SDK
#  - explicit: '1':   manual; you bump when you change native code

# 6) When OTA is NOT enough — REBUILD the binary
# - Added a new native module (any react-native-* with native code)
# - Changed Info.plist / AndroidManifest permissions
# - Bumped Expo SDK / React Native version
# - Toggled architecture (Hermes on/off, new arch on/off)
# The Updates.checkForUpdateAsync call will refuse to install a bundle whose
# runtimeVersion does not match the binary — failing safe.

# 7) Rollouts — staged percentage
# npx eas update --branch production --message 'New checkout' --rollout-percentage 5
# npx eas update:edit --branch production --rollout-percentage 25
# npx eas update:edit --branch production --rollout-percentage 100
# Monitor Crashlytics during each step.

# 8) Roll back instantly
# npx eas update:rollback-to-embedded --branch production
# Reverts the channel to the binary's built-in bundle; devices fetch nothing new on next launch.

# 9) View what is live per channel
# npx eas update:list --branch production
# npx eas channel:view production

# 10) CI / release flow
# - Tests pass on the protected branch
# - Build the binary on a tag (or when runtimeVersion bumps)
# - Ship JS-only updates via 'eas update' on every merge to main:
# - name: ship-OTA
#   run: npx eas-cli update --branch production --auto
#   env: { EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} }

# 11) Privacy + compliance
# - App Store review reviews the BINARY, not the OTA bundle
# - You may not ship code that materially changes app behaviour or content
#   policy compliance via OTA (Apple guideline 4.3.4)
# - Keep OTA to bug fixes, copy changes, and feature flag toggles

# 12) When NOT to ship an OTA on a Friday
# - You changed a deep navigation flow
# - The change relies on a recent binary that not all users have
# - Your monitoring is quiet over the weekend
# (Treat OTAs like any other deploy: stage, monitor, roll back fast.)

Why it matters

`runtimeVersion: { policy: "appVersion" }` is the single setting that keeps OTA safe. Bump the app version when you add native code, and old binaries automatically refuse the new bundle — no crash, no "missing native module" reports from users on yesterdays build. Skip this and your worst OTA mistake becomes a fleet-wide crash loop.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Ship a JS-only update without re-submitting to the stores.
eas update --branch production --message "hotfix login screen"
Try it Yourself »

Discussion

Loading…