OTA updates
Push JS updates without a new build.
OTA = over-the-air updates. Ship a JS bundle change to every user without re-submitting to the store. Your apps update overnight while users sleep. This is the Expo superpower most teams underuse.
What can update OTA, what can't
Your app has two layers:
- Native code — installed via APK/IPA. Changing it requires a new store submission.
- JS bundle — your React Native code, components, styles. Updates OTA.
What that means in practice:
| Change | OTA? |
|---|---|
| UI / styles / new screens | Yes |
| Bug fixes in JS / TS | Yes |
| API contract changes (calling new endpoints) | Yes |
| Adding a new npm dep that's pure-JS | Yes |
| Adding a native module (camera, BLE, etc.) | No — store re-submit |
| Changing app icon or splash screen | No — store re-submit |
| Bumping Expo SDK | No — store re-submit |
Shipping an OTA
# from apps/mobile/$eas update --branch production --message "Fix dashboard refresh bug"
Bundles your JS, uploads to Expo. Apps configured for production branch check for updates on next launch and download the new bundle.
Branches map to release channels
Set up at least two branches:
- preview — internal testers / beta users
- production — store users
Push to preview first, validate, then push to production. Same idea as canary / staging / prod for backends.
What the user experiences
- User opens the app
- Expo checks for new bundle (~200ms HTTP call)
- If new bundle available, downloads it in the background
- Next launch uses the new bundle
First-launch users see the bundled bundle (from the store). On their second launch, they see your update.
Forcing an immediate reload
For urgent fixes, you can ask Expo to fetch + reload on this session, not next launch:
import * as Updates from 'expo-updates'useEffect(() => {Updates.checkForUpdateAsync().then((update) => {if (update.isAvailable) {Updates.fetchUpdateAsync().then(() => Updates.reloadAsync())}})}, [])
Use sparingly — reloading mid-session is jarring. Better for important security fixes.
Rolling back
Pushed a bad update? Republish the previous version's bundle:
$eas update --branch production --republish --group <old-group-id>
Every eas update creates a "group" you can re-publish later. Roll back to any earlier group, users get it on next launch.
Quick check
Try it
Ship your first OTA:
- Make a small visible change to your app (e.g., change the home-screen title from "Users" to "Team").
eas update --branch preview --message "rename users to team"- On your phone (where you installed the preview build), force- quit the app and reopen it.
- You should see the new title without re-installing.
Paste a before/after screenshot in notes.md. That's chapter 5's assignment done.
You finished Building Mobile with Go API 🎉
Five chapters, ~13 lessons. You can now scaffold a mobile + API monorepo, sync types end-to-end, build login + secure storage + refresh, register and receive push notifications, ship to the stores via EAS, and OTA updates over the air.
From here: pair this with --triple --mobile in the Multi-Platform course to add web + admin to the mix, or move to Building Web with Next.js to add a marketing site + dashboard.
Spot a typo? Have an idea?
Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.
Suggest an improvement on GitHub