Migrating from Expo Updates
Migrating from Expo Updates
This guide covers moving from Expo Updates / EAS Update to self-hosted Codemagic Patch. You can keep EAS Build for native binaries if you want; only the update delivery layer changes. Start against the local evaluation stack with your own app before provisioning domains. For trade-offs, see How Patch compares.
Patch requires a development build (not Expo Go) and a new native binary with the Patch SDK embedded. Plan a store or internal build before OTA can flow through Patch.
Concept mapping
| Expo Updates | Codemagic Patch |
|---|---|
| EAS-hosted update server | Self-hosted Patch API + storage |
Update channel (preview, production, …) | Deployment (Staging, Production, …) |
runtimeVersion (EAS) | targetBinaryVersion |
eas update | cmpatch release-react |
Updates.checkForUpdateAsync() / fetchUpdateAsync() | sync() or checkForUpdate() + downloadUpdate() + installUpdate() |
Updates.reloadAsync() | restartApp() (or install mode IMMEDIATE) |
expo-updates config in app.config | Config plugin for CNG or manual native configuration |
| EAS project credentials | OAuth sign-in + cm_pat_… tokens on your Patch instance |
1. Stand up Patch
For a proof of concept on your own Expo app, start with the Local quickstart (cmpatch selfhost local-eval). That runs the real API, storage, and dashboard on your laptop without domains or OAuth. Use Install when you want a shared Staging/Production server.
| Local eval | Running on VM | |
|---|---|---|
API (apiUrl) | http://localhost:3000 | https://updates.example.com |
Download base (downloadBaseUrl) | http://localhost:9100/codemagic-patch | https://storage-updates.example.com/codemagic-patch |
Create apps for your product (skip the seeded demo-app-* apps unless you are only trying the bundled on-device demo):
cmpatch init # from your Expo project root: signs in, creates one app per platform, links the project
cmpatch deployment list --app MyApp-iOS --format table
Map each EAS channel you use today to a Patch deployment (e.g. EAS preview → Patch Staging, EAS production → Patch Production). Use each deployment key in the native integration you choose in the next step. To build Staging and Production binaries from one project, see Build for different deployments.
Local eval is enough to answer "does Patch work on our app?" It is not a production host: authentication is disabled and ports bind to localhost. Physical devices and Android emulators need a LAN IP (or adb reverse); see Try it with your own app.
2. Replace the client integration
Choose how to apply the native changes before running any generation commands:
- Generated native projects (Expo CNG): if your native customizations are represented in app config and config plugins, use the config plugin path below.
- Manually maintained native projects: if you edit
ios/orandroid/directly and those changes cannot be regenerated from config, use the manual path. Whether the directories are committed to Git does not determine which path to use.
Expo documents Prebuild as optional. You can keep using Expo and EAS Build while maintaining native projects yourself.
Remove Expo Updates from the project:
yarn remove expo-updates
Remove expo-updates from plugins in app.config and delete updates / runtimeVersion blocks that pointed at EAS (unless you still need runtimeVersion for another tool. Patch does not read it).
While expo-updates is still declared, the wiring cmpatch init runs reports it as a conflict and leaves the native steps for later; with it removed, run cmpatch wire to install the SDK and apply the platform configuration for either path below, or follow the manual steps.
Manually maintained native projects
Skip Prebuild and keep your existing native projects. Remove the previous expo-updates integration:
- In iOS
Expo.plist, remove theEXUpdates*settings. - In Android
AndroidManifest.xml, remove theexpo.modules.updates.*metadata and any resources used only by those entries, such asexpo_runtime_version. - Remove any manually added
expo-updatesbuild hooks or custom initialization code. Older integrations may referencecreate-manifest-ios.shin an Xcode build phase orcreate-manifest-android.gradleinapp/build.gradle; remove those references if present, since the package is no longer installed. Keep the shared Expo modules, host wrappers, and bundling setup.
Follow manual native setup to add Patch's configuration and bundle selection using the Expo-specific diffs to preserve your host structure, then run pod install in ios/ to refresh native dependencies. Adding the Patch config plugin to app config alone does not update manually maintained native files.
For EAS Build, ensure the maintained native directories are included in the upload: check .gitignore and .easignore (Expo upload rules). If the platform's native directory is absent, EAS Build generates it with Prebuild and your manual Patch changes will be missing. Commit the native projects so they are available in fresh checkouts; npx expo run:ios / npx expo run:android also generate the corresponding native project when it is absent.
Continue with Build and update the JavaScript integration.
Generated native projects
Configure the Expo config plugin with deployment key, apiUrl, and downloadBaseUrl per platform. For local eval:
{
"expo": {
"plugins": [
[
"@codemagic/react-native-patch",
{
"ios": {
"deploymentKey": "<from deployment list>",
"downloadBaseUrl": "http://localhost:9100/codemagic-patch",
"apiUrl": "http://localhost:3000"
},
"android": {
"deploymentKey": "<from deployment list>",
"downloadBaseUrl": "http://localhost:9100/codemagic-patch",
"apiUrl": "http://localhost:3000"
}
}
]
]
}
}
npx expo prebuild --clean deletes and regenerates ios/ and android/; direct edits not reproduced by app config or plugins will be lost. Commit or back up your work first, and follow the native setup guidance before switching a manually maintained project to Prebuild.
Then regenerate native projects:
npx expo prebuild --clean
cd ios && pod install && cd ..
Build and update the JavaScript integration
Build a development or release binary (not Expo Go) and install it on a simulator or emulator. Replace update logic in JS:
// Before (Expo Updates)
import * as Updates from "expo-updates";
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
// After (Patch)
import { sync } from "@codemagic/react-native-patch";
void sync();
Tune when checks run in Checking for updates and install timing in Applying updates.
3. Ship OTAs with Patch instead of EAS Update
For a local proof of concept, run release-react from your machine against the eval stack (no CI token required):
cmpatch release-react \
--server-url http://localhost:3000 \
--platform ios \
--deployment Staging \
--release-notes "Local PoC" \
--yes
Confirm the update on the binary you built in step 2 (check / relaunch, same loop as the on-device demo). That is enough to decide whether a production install is worth it.
When you cut over for real, install the CLI against your hosted API and create a CI token:
npm install -g @codemagic/patch-cli
cmpatch init # re-run in the project to point it at the production server
cmpatch token create --name ci
Replace eas update (or EAS Update workflow steps) with the same release-react command, pointed at your production server URL. release-react bundles JS, resolves a target binary version, and uploads in one step, similar in role to eas update plus local bundling. See CI integration.
You can keep EAS Build (or any CI) to produce the native .ipa / .apk.
If your app reads EXPO_PUBLIC_* variables, set them in the release-react job too: cmpatch release-react bundles with the environment of the invoking shell and does not read EAS build profiles. See Keep bundle environment in sync with the native build.
4. Cut over
- Install Patch on your domains (or keep using local only until the PoC is done).
- Set the production API and download URLs in your Expo plugin or manually maintained native configuration; ship a development or store build with the appropriate deployment keys (Staging first).
- Publish an OTA with
cmpatch release-reacttargeting the binary version users install. - Confirm updates on real devices; then promote the same workflow to Production.
- Turn off EAS Update for the project once no binaries depend on it.
Checklist
- Local eval (or hosted Install) running; apps and deployments created
-
expo-updatesremoved, including its previous native integration when maintaining native projects manually - Chosen integration completed (local URLs for PoC): CNG — configure the Patch plugin and run
expo prebuild; manual — apply Patch's native configuration and bundle selection directly - New development/release binary built and installed (not Expo Go)
-
sync()(or manual API) replaces Expo Updates calls - Local
cmpatch release-reactlands an update on that binary - Hosted Install done before any shared/Staging cutover
- CI publishes with
cmpatchinstead ofeas update - New native binary in users' hands; Staging validated before Production