OTA Updates Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
OTA is JS and assets only: Native modules, config plugins, permissions, entitlements, SDK upgrades, and app icons require eas build and store submission - OTA cannot add native symbols.
Set explicit runtimeVersion policy: Use appVersion, nativeVersion, or fingerprint consistently in app.config - document the choice in README and never mix policies across flavors without an ADR.
Match runtime on build and update: The runtimeVersion resolved by eas build must equal what eas update targets - verify with npx expo config --type public before every publish.
Bump runtime when native changes: New native dependency, plugin edit, or permission string means new store build and new runtime line - do not OTA JS that imports missing native modules.
Configure updates.url and projectId: Run eas update:configure after eas init - missing updates.url disables expo-updates silently in release builds.
Wire channel headers to build profiles: updates.requestHeaders["expo-channel-name"] must match eas.json channel for each profile - drive from EAS_BUILD_PROFILE in dynamic app.config.
Use eas update --environment always: On SDK 55+, update runners ignore local .env - pass --environment production (or preview) so OTA bundles match build env vars.
Name channels for environments: development, preview, staging, production - not engineer names; map channels to audiences in docs/release-channels.md.
Linear promotion flow: development → preview (QA) → production - no skipping preview for payment, auth, or checkout changes regardless of diff size.
Preview builds for OTA sign-off: QA tests on EAS preview profile binaries, not Expo Go - Expo Go cannot receive your production update configuration.
CI-only production publish: Prefer tagged GitHub Actions workflows for eas update --channel production - laptop publish is break-glass with second approver and postmortem.
Record last known-good update ID: Every production release ticket stores the previous bundle ID for eas update:rollback or eas update:republish - rollbacks without this are guesswork.
Define rollback triggers before launch: Crash-free drop, payment success drop, and auth error spike thresholds with owners - see Release & OTA Rules Tier 4.
Practice rollback quarterly on preview: Run eas update:rollback on the preview channel in a drill - target under 15 minutes from decision to verified channel head.
Gate native-dependent JS with flags: Remote kill switches plus Application.nativeApplicationVersion checks prevent crashes until store adoption crosses your threshold.
Log updateId in production analytics: Updates.updateId, runtimeVersion, and channel at session start - support and incident response depend on knowing the running bundle.
checkAutomatically ON_LOAD with fallback 0: Check on cold start without blocking launch on slow networks - tune reload UX separately via expo-updates client API.
Rebuild after first updates config: Adding expo-updates wiring requires a new native binary - eas update alone does not enable updates on old installs.
Treat bundle size regressions as releases: Large OTA bundles hurt TTI - compare Metro output before production publish and link to performance budget rules.
Backward-compatible API contracts: Mobile users skip updates - JS shipped OTA must tolerate older API responses until store adoption is high; run contract tests in CI.
Safe local storage migrations: MMKV and SQLite schema bumps need tested upgrade paths from the previous production bundle - bad migrations brick installs and need rollback.
Staged rollouts on production: Use --rollout-percentage or eas channel:rollout after preview soak - monitor crash and payment metrics at each step before 100%.
Tag Sentry with updateId: Segment crash reports by bundle during rollouts - without cohort tags, percentage rollouts are blind.
Track runtime distribution weekly: Percentage of users per runtimeVersion - stale binaries stop receiving OTAs; enforce minimum native version when security requires.
Two-track release checklist: Store submission checklist for native changes and OTA checklist for preview soak, flags, rollback ID, and dashboards - attach both to every release ticket.
appVersion runtime policy + eas.json channels per profile + eas update:configure + preview soak + CI production publish + updateId logging + quarterly rollback drill + Release & OTA Rules checklist.
No - native crashes need a new store build. OTA fixes JavaScript logic and assets only. Roll back JS if the crash is from importing a missing native module, then ship binary.
OTA Updates Basics for boundaries, then expo-updates Configuration for app.config, then Release Channels & Branches for promotion flow.
appVersion for most consumer apps. fingerprint when native project drifts without marketing version bumps - see Runtime Version Policy.
Rollouts limit blast radius before full exposure. Rollback Runbook recovers after a bad publish - use both with shared monitoring dashboards.
app.config cookbookStack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026