Intro
Upgrading an Expo project is more than bumping a version number in package.json. It is a coordinated change across the Expo SDK, React Native, native projects, and often your CI/CD pipeline. Without a clear process, you risk broken builds, incompatible native modules, and unexpected runtime failures. This guide provides a practical, command-driven approach to Expo upgrades and migrations, designed for developers, DevOps consultants, and technical startup teams.
We will cover:
- How to inventory your current Expo and React Native environment.
- The safest path to perform an upgrade, including preflight checks and dependency alignment.
- Verification and diagnostics to confirm the upgrade worked.
- Common failure modes and how to recover from them.
- A concise operations checklist you can reuse for future upgrades.
Throughout, we emphasize operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify the result, and document recovery steps before you need them. You will find concrete commands with expected outputs, failure signals, and rollback paths.
Version and Environment Inventory
Before touching any code, build a complete picture of your current setup. The goal is to know exactly what you have, what is supported, and what the upgrade target requires. This reduces surprises and gives you a baseline for rollback.
Identify Current Versions
Run these commands in your project root (the directory containing package.json):
npx expo --version
Expected output (example for SDK 46):
4.12.0
node -v
Expected output (must meet Expo's minimum, e.g., v16.13.0 for SDK 46):
v16.13.0
npx react-native --version
Expected output:
0.69.6
Check the exact Expo SDK version in your package.json:
cat package.json | grep '"expo"'
Example line:
"expo": "~46.0.0"
Also inspect all React Native related dependencies:
npm list react react-native expo
Expected output (truncated):
[email protected] /path/to/project
├── [email protected]
├── [email protected]
└── [email protected]
Capture Deployment Topology
Know where your app runs. Are you using Expo Go, a development build, or a standalone binary? This affects upgrade strategy and testing.
Check your app.json or app.config.js for expo.ios and expo.android settings, and note if you are using EAS Build or local builds. For example, a typical app.json snippet:
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"ios": {
"bundleIdentifier": "com.example.myapp"
},
"android": {
"package": "com.example.myapp"
}
}
}
If you use EAS, run:
eas build:list --platform all --limit 5
This lists recent builds and their Expo SDK versions, helping you ensure all builds use the same version before upgrading.
Check Compatibility with Target SDK
Before upgrading, consult the official Expo upgrade guide for your target SDK. For example, if moving from SDK 46 to 47, check the Expo SDK 47 changelog for breaking changes and required React Native version. Note any changes to core packages like expo-av, expo-camera, or expo-notifications, as their APIs may have changed.
Document the current state in a simple table for clarity:
| Component | Current Version | Target Version | Compatible? |
|---|---|---|---|
| Expo SDK | 46.0.0 | 47.0.0 | Yes |
| React Native | 0.69.6 | 0.70.5 | Yes |
| Node.js | 16.13.0 | >=16.13.0 | Yes |
| expo-camera | ~12.0.0 | ~13.0.0 | Needs API review |
This inventory becomes your rollback reference. Save a copy of package.json and, if applicable, your native folders (ios/ and android/) in version control before making changes.
Safe Configuration Path
The safest upgrade path uses Expo's official tools and follows a controlled sequence. We will use expo upgrade to align dependencies, then manually adjust configuration if needed.
Prerequisites and Preflight Checks
Before running the upgrade, ensure your working directory is clean and all changes are committed:
git status --porcelain
Expected output (empty if clean):
If changes exist, commit or stash them. Then create a backup branch:
git checkout -b upgrade/sdk-47
Verify that you have a recent backup of your project, especially if you are not using version control. A simple copy command:
cp -R /path/to/project /path/to/project-backup-sdk46
Note: Never store real credentials, tokens, or private keys in your repository. Use environment variables or .env files that are excluded from version control.
Run the Expo Upgrade
Use the official upgrade command:
npx expo upgrade
This command will:
- Determine the latest compatible SDK version.
- Update
package.jsondependencies to the recommended versions. - Install the new dependencies with npm or yarn (depending on your lock file).
- Run any necessary post-install scripts.
Expected output (example excerpt):
? You are currently using SDK 46.0.0. Would you like to upgrade to SDK 47.0.0? Yes
Updating packages...
- expo: 46.0.0 -> 47.0.0
- react-native: 0.69.6 -> 0.70.5
- expo-camera: 12.0.0 -> 13.0.0
...
Installing dependencies...
added 25 packages, removed 12 packages, changed 40 packages
If you need to upgrade to a specific SDK version, use:
npx expo upgrade 47.0.0
After the automatic upgrade, inspect the changes in package.json with:
git diff package.json
Verify that all versions match the target SDK's expected versions. For SDK 47, the expected key versions are:
- Expo:
~47.0.0 - React Native:
0.70.5 - React:
18.1.0
Manual Configuration Adjustments
Some upgrades require manual changes to configuration files. For example, in SDK 47, the expo-splash-screen configuration moved to a plugin. You may need to update app.json as follows:
Before (SDK 46):
{
"expo": {
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
}
}
After (SDK 47):
{
"expo": {
"plugins": [
[
"expo-splash-screen",
{
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
]
]
}
}
Check the upgrade guide for any breaking changes that require your attention. For instance, if you use expo-av, note that in SDK 47, the Video component's useNativeControls prop was deprecated in favor of useNativeControls inside videoStyle. Update your code accordingly.
Blast Radius Limitation
To limit the impact of the upgrade, consider doing it in stages if you have a large project. For example, first upgrade only the core Expo packages and React Native, then separately upgrade dependent libraries like expo-camera or expo-notifications. This makes it easier to isolate issues.
After any change, run the verification steps in the next section before moving on.
Verification and Diagnostics
After the upgrade, you must verify that the app builds and runs correctly. This involves static checks, bundling, and possibly running the app on a simulator or device.
Static Checks
TypeScript Check
If your project uses TypeScript, run:
npx tsc --noEmit
Expected output (if no errors):
If errors appear, address them. For example, a common error after upgrading to SDK 47 with expo-camera:
Type 'Camera' is missing the following properties from type 'CameraType': getSupportedRatiosAsync, ...
This indicates that the library API changed, and you need to update your usage.
Lint Check
Run your linter (e.g., ESLint):
npx eslint .
Expected output:
If warnings, prioritize those related to new deprecations or changed APIs.
Bundle Check
Generate a production bundle to catch module resolution errors:
npx expo export --platform all
Expected output:
Exporting iOS bundle...
Exporting Android bundle...
Export successful
If the export fails, the error message will point to the problematic module. For example:
Unable to resolve module 'some-package' from 'src/App.tsx'
This usually means a package is missing or incompatible with the new SDK.
Runtime Verification
If possible, run the app using Expo Go or a development build on a simulator/emulator.
Start the dev server:
npx expo start
Then press i for iOS simulator or a for Android emulator. Observe the app launching. Test key features, especially those using upgraded libraries.
For a more thorough check, use EAS Build to create a development build and test on a real device.
Diagnostics Commands
If the app fails to start, gather diagnostics:
npx expo-doctor
Expected output (healthy):
✔ Check Expo config
✔ Check package.json
✔ Check dependencies
✔ Check for common project issues
All checks passed
If issues found, expo-doctor provides suggestions. For example, if there are duplicate dependencies:
✖ Check dependencies
Found duplicated packages: react-native, react
Run `npx expo install --fix` to resolve.
Run the fix command:
npx expo install --fix
Check native logs if using a development build:
npx react-native log-ios
or
npx react-native log-android
Look for errors related to native module registration or missing modules.
Failure Modes and Recovery
Even with careful planning, upgrades can fail. Here are common failure modes, their symptoms, and recovery steps.
Dependency Version Conflicts
Symptom: npm install or yarn install fails with peer dependency errors.
Example error:
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react-native
npm ERR! react-native@"0.70.5" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react-native@"^0.69.0" from [email protected]
Recovery:
- Ensure all Expo-related packages are updated to versions compatible with the new SDK. Use
npx expo installto get recommended versions.
npx expo install expo-camera
- If necessary, manually adjust
package.jsonand runnpm installwith--forceor--legacy-peer-depsas a temporary measure, then resolve conflicts properly.
Native Module Incompatibility
Symptom: App builds but crashes at runtime, often with a red box error like:
Invariant Violation: Native module cannot be null.
or
TypeError: null is not an object (evaluating 'NativeModules.SomeModule')
Recovery:
- Identify the module causing the issue (e.g.,
ExpoCamera). - Check if the module's native code is included in the build. For development builds, ensure you have rebuilt after adding the package.
expo run:android
or
expo run:ios
- If using a custom development client, rebuild it with the new modules.
Bundling Errors
Symptom: Metro bundler fails with "Unable to resolve module" or syntax errors.
Example:
Unable to resolve module 'react-native-gesture-handler' from 'src/navigation/AppNavigator.tsx'
Recovery:
- Install the missing package with
npx expo install react-native-gesture-handler. - If the module exists but is incompatible, clear Metro cache:
npx expo start --clear
- If the error references a transpilation issue (e.g., unexpected token import), check your
babel.config.js. Ensure it includesbabel-preset-expoand any necessary plugins.
Configuration Drift
Symptom: App behavior changes after upgrade, such as splash screen not showing or permissions not requested.
Recovery:
- Review the Expo SDK changelog for changes to configuration plugins. For example, in SDK 47, several modules now require explicit plugin configuration in
app.json. - Compare your
app.jsonwith the default configuration generated bynpx expo prebuild(if using CNG) or refer to the documentation. - Re-run
npx expo prebuild --cleanto regenerate native projects with current configuration. Be cautious as this will overwrite native changes.
Rollback Strategy
If the upgrade causes unresolvable issues, roll back to the previous version.
- Stop any running processes.
- If you created a backup branch, switch back:
git checkout main
- Restore the original
package.jsonand native folders from backup:
git checkout main -- package.json ios android
- Reinstall dependencies:
rm -rf node_modules
npm install
- Verify with
npx expo-doctorand a bundle check.
If you used a full project backup (folder copy), simply replace the project folder with the backup.
Operations Checklist
Use this checklist for every Expo upgrade to ensure consistency and reduce risk. Each item includes a representative example with owner and expected outcome.
Pre-Upgrade
- [ ] Backup current state: Create a git branch and/or folder backup. (Owner: Developer, e.g., Alex Chen,
git checkout -b backup/sdk46) - [ ] Record current versions: Run
npx expo --version,node -v,npx react-native --version. Store output in a document. (Owner: DevOps, e.g., Priya Shah) - [ ] Review target SDK changelog: Note breaking changes and required updates. (Owner: Tech Lead, e.g., Jordan Lee)
- [ ] Check CI/CD configuration: Ensure build pipeline is compatible with new versions. Update if necessary. (Owner: DevOps)
- [ ] Verify clean working directory:
git statusmust show no uncommitted changes. (Owner: Developer)
Upgrade Execution
- [ ] Run upgrade command:
npx expo upgrade(or specific version). (Owner: Developer) - [ ] Resolve dependency conflicts: Use
npx expo install --fixif needed. (Owner: Developer) - [ ] Apply manual configuration changes: Update
app.json,babel.config.js, etc., per changelog. (Owner: Developer) - [ ] Update code for breaking API changes: Adjust component usage. (Owner: Developer)
Verification
- [ ] Run static checks:
npx tsc --noEmit,npx eslint .. Ensure zero errors. (Owner: Developer) - [ ] Export bundle:
npx expo export --platform all. Confirm success. (Owner: Developer) - [ ] Run on simulator/device: Test critical features. (Owner: QA, e.g., Sam Rodriguez)
- [ ] Run
npx expo-doctor: Confirm all checks pass. (Owner: Developer) - [ ] Performance smoke test: Compare app startup time and memory usage before/after upgrade. Record metrics. (Owner: QA)
Post-Upgrade
- [ ] Update documentation: Note new versions and any configuration changes in README. (Owner: Tech Writer, e.g., Morgan Smith)
- [ ] Commit changes: Create a meaningful commit message, e.g., "Upgrade to Expo SDK 47". (Owner: Developer)
- [ ] Tag release: Tag the commit for easy rollback reference, e.g.,
git tag sdk-47. (Owner: Release Manager) - [ ] Monitor production: Observe error rates and crashes via your analytics. (Owner: SRE, e.g., Taylor Brown)
Conclusion
Expo upgrades are manageable when you follow a structured process: inventory your environment, use the official upgrade tool, verify thoroughly, and know your rollback options. This guide has provided concrete commands, expected outputs, and recovery paths to help you navigate the process with confidence.
The key takeaways are:
- Observe before changing: Always capture current state and versions.
- Limit blast radius: Use version control, branches, and staged upgrades.
- Verify with real commands: Use
expo-doctor, TypeScript checks, bundle exports, and runtime tests. - Document recovery: Have a tested rollback plan.
As a next step, choose a low-risk verification for your current Expo project. Run the inventory commands and record the results. Then, when you are ready to upgrade, follow the checklist and adapt the examples to your specific dependencies. For further reading, consult the official Expo upgrade documentation and the changelog for your target SDK.
Remember: a reliable technical workflow makes failure visible, protects sensitive values, limits changes to the intended resource, and defines recovery verification before an incident forces the decision.