Your first update
Push a JavaScript-only fix to installed apps with EAS Update, watch it arrive, and roll it back if you need to.
Hey - by the end of this page a change you make lands on an installed build without a new build and without App Review, you've watched the "Update ready" banner pick it up, and you know how to take it back.
EAS Update ships your JavaScript bundle and assets. Anything native - a library with native
code, a config plugin, a change in app.config.ts or the app ids in readynative.config.ts, a
new icon - still needs a build.
Before you start
- Tier: Starter or Pro. The Free tier ships without
expo-updates. - Time: about 15 minutes.
- Runs in: a preview or production build installed on a phone, made after the EAS project was linked. Expo Go and dev builds don't take channel updates - they run whatever your dev server serves - and the updates banner is off in dev.
- Accounts: the Expo account your EAS project lives in.
- Previous tutorial: Ship to Google Play or Ship to TestFlight - you need a build from one of them.
1. Check updates are wired
app.config.ts turns app.easProjectId into updates.url (https://u.expo.dev/<projectId>)
and leaves updates out entirely while the id is empty.
bun run doctorYou should see: green rows for "EAS project linked" and "app.config.ts updates.url". If the build on your phone was made before the id was set, it can never receive an update - make a new build first.
2. Make a change
Pick something you'll recognise on sight. After setup, the Home tab is a plain welcome - change
its "Edit src/screens/home/home-screen.tsx to start building." line (or any visible string, if
you've replaced Home):
<Text>Shipped over the air</Text>You should see: the new line in your dev build or Expo Go. The installed preview or production build still shows the old one.
3. Publish it
Every eas.json profile has a channel - development, preview, production - and each update
goes to one channel. Send it to the channel of the build on your phone:
bunx eas-cli update --channel preview --environment preview --message "Home heading"For a production build, --channel production --environment production.
--environment is required. It picks the EAS environment whose EXPO_PUBLIC_* values are
inlined into the update - the same set the matching build used - and ignores your local .env,
so the keys must be on EAS (Environment variables → On EAS).
secret variables aren't available to an update. Using Sentry? Upload the update's source maps
right after with bunx sentry-expo-upload-sourcemaps dist, or its stack traces stay minified.
You should see: the CLI export the bundle, upload it, and print the update group id and a link to it on expo.dev.
4. Confirm it's published
bunx eas-cli update:list --branch previewA channel points at the branch of the same name, which the first eas update --channel
created.
You should see: your update at the top, with your message and the runtime version (your app
version, since runtimeVersion.policy is appVersion).
5. Watch it arrive
Open the app on your phone. It checks for an update on launch and downloads it in the background without blocking startup, so the launch you're looking at still runs the old bundle.
- With the updates banner (
features.updatesBanner: trueinreadynative.config.ts, the default): an Update ready banner appears once the download finishes, with Later and Restart. Tap Restart. The banner also re-checks when the app comes back to the foreground, at most every five minutes. - Without it: close the app completely and open it again. The downloaded update applies on that cold start.
You should see: the new line on the installed build, with no store involved.
Updates follow the app version
An update only reaches builds with the same runtime version - here, the version in
package.json. Bump it whenever native code changes (npm version minor, then a new build), and
older builds keep getting only the updates published for their version.
6. Roll it back
If an update misbehaves, take it back:
bunx eas-cli update:rollbackThe CLI asks which kind: back to the previously published update (it's republished), or back to the update embedded in the build (the JavaScript the binary shipped with). Apps pick up the rollback the same way they picked up the update - on the next launch, or through the banner.
You should see: after a restart, the old line back on the installed build.
Check it
bunx eas-cli update:list --branch previewThe rollback shows up as the newest entry. The next update you publish reaches every build on that channel as usual.
If it doesn't work
- The update never arrives - the channel doesn't match the build (a preview build listens to
preview), or the runtime versions differ (the build's app version isn't the one inpackage.jsonnow). Compare them withbunx eas-cli update:list. - No banner - you're in Expo Go or a dev build,
features.updatesBanneris off, or the build was made withoutapp.easProjectId. Close and reopen the app twice instead. - The update arrived but a service says "Configure …" - the key isn't in the EAS environment
you passed to
--environment. See A key works locally but not in an EAS build. - The app crashes after an update - the change probably needs native code the build doesn't have. Roll back (step 6), then ship the change as a new build with a bumped version.
More in Troubleshooting.
Congrats 🎉
You shipped a fix in minutes instead of days, watched it land, and know how to undo it. That closes the loop: build once, update often, rebuild only when something native changes.
That's the last tutorial. Before your first store release, read Pass app review.