Skip to main content

Overview

This guide covers migrating to Expo from various starting points and strategies for smooth upgrades between Expo SDK versions.

Migrating from React Native CLI

If you have an existing React Native CLI project, you can adopt Expo incrementally.
1

Install Expo packages

npm install expo
npx expo install expo-constants expo-device expo-file-system
2

Update package.json scripts

package.json
{
  "scripts": {
    "start": "expo start",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  }
}
3

Create app.json

app.json
{
  "expo": {
    "name": "Your App",
    "slug": "your-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.yourapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.yourcompany.yourapp"
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}
4

Update entry point

If you have a custom entry point, update it:
index.js
import { registerRootComponent } from 'expo';
import App from './App';

registerRootComponent(App);
5

Regenerate native projects

npx expo prebuild --clean
This updates your ios/ and android/ directories with Expo configuration.
6

Test the app

npx expo run:ios
npx expo run:android

Handling native dependencies

Most React Native libraries work with Expo:
# Install using expo install to get compatible versions
npx expo install react-native-reanimated react-native-gesture-handler

Replace React Native CLI libraries

npm uninstall @react-native-async-storage/async-storage
npx expo install expo-secure-store
Update imports:
// Before
import AsyncStorage from '@react-native-async-storage/async-storage';

// After
import * as SecureStore from 'expo-secure-store';

Migrating from Native (iOS/Android)

If you’re coming from native development:
1

Create Expo app

npx create-expo-app@latest my-app --template blank-typescript
cd my-app
2

Set up navigation

npx expo install expo-router react-native-safe-area-context react-native-screens
3

Port UI components

Convert native UI to React Native:
// iOS UIKit -> React Native
UILabel -> <Text>
UIButton -> <TouchableOpacity> or <Button>
UIView -> <View>
UIImageView -> <Image>
UIScrollView -> <ScrollView>
UITableView -> <FlatList>
// Android Views -> React Native
TextView -> <Text>
Button -> <Button>
ViewGroup -> <View>
ImageView -> <Image>
ScrollView -> <ScrollView>
RecyclerView -> <FlatList>
4

Port business logic

Move business logic to TypeScript:
utils/api.ts
export async function fetchUserData(userId: string) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  return response.json();
}
5

Add native modules if needed

For platform-specific features:
npx create-expo-module@latest --local my-native-module

Migrating from Flutter

FlutterReact Native / Expo
Text()<Text>
Container()<View>
Row()<View style={{ flexDirection: 'row' }}>
Column()<View>
Image.network()<Image source={{ uri: url }}>
GestureDetector()<TouchableOpacity>
ListView.builder()<FlatList>
Stack()<View> with absolute positioning

Upgrading Between SDK Versions

Automated upgrade

# Upgrade to latest SDK
npx expo install --fix

# Or upgrade to specific version
npm install expo@^50.0.0
npx expo install --fix
This updates all Expo packages to compatible versions.

Manual upgrade process

1

Check the upgrade guide

Visit Expo SDK release notes for breaking changes.
2

Update Expo SDK

npm install expo@^50.0.0
3

Update dependencies

npx expo install --fix
4

Update app.json

app.json
{
  "expo": {
    "sdkVersion": "50.0.0"
  }
}
5

Clear caches

rm -rf node_modules
npm install
npx expo start --clear
6

Rebuild native projects

npx expo prebuild --clean
npx expo run:ios
npx expo run:android

Common breaking changes

  • Expo Router becomes the default routing solution
  • New App Icon and Splash Screen API
  • Updated minimum iOS version to 13.4
  • New EAS Update API
  • React Native 0.73
  • New Architecture support (Fabric)
  • Updated Metro bundler
  • Improved web support
  • React Native 0.71
  • Hermes is now the default JS engine
  • Improved TypeScript support
  • New Expo Image component

Migration Strategies

Incremental migration

Migrate screen by screen:
// Keep existing navigation
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

// Add Expo Router gradually
import { router } from 'expo-router';

function OldScreen() {
  return (
    <Button 
      title="Go to new screen"
      onPress={() => router.push('/new-screen')}
    />
  );
}

Feature flags

Use feature flags for gradual rollout:
utils/features.ts
export const features = {
  useNewAuth: __DEV__ ? true : false,
  useExpoRouter: true,
  useNewAPI: false,
};
import { features } from './utils/features';

if (features.useNewAuth) {
  // New authentication flow
} else {
  // Legacy authentication
}

Parallel implementations

Run old and new implementations side-by-side:
import { Platform } from 'react-native';

if (Platform.OS === 'web') {
  // New web implementation
} else {
  // Existing mobile implementation
}

Rollback Strategy

If something goes wrong:
1

Use version control

git checkout -b migration/expo-sdk-50
# ... make changes ...
# If issues arise:
git checkout main
2

Keep old versions

package.json
{
  "devDependencies": {
    "expo-sdk-49": "npm:expo@^49.0.0"
  }
}
3

Document migration

Keep notes on changes for easy rollback:
MIGRATION.md
# Migration to Expo SDK 50

## Changes Made
- Updated expo-router from 2.x to 3.x
- Replaced AsyncStorage with expo-secure-store
- Updated minimum iOS version to 13.4

## Rollback Steps
1. `git checkout main`
2. `npm install`
3. `npx expo prebuild --clean`

Testing After Migration

Automated tests

__tests__/migration.test.ts
import { render, fireEvent } from '@testing-library/react-native';
import HomeScreen from '../app/index';

describe('Migration tests', () => {
  it('renders correctly after migration', () => {
    const { getByText } = render(<HomeScreen />);
    expect(getByText('Welcome')).toBeTruthy();
  });

  it('navigation works with Expo Router', () => {
    const { getByText } = render(<HomeScreen />);
    fireEvent.press(getByText('Go to Settings'));
    // Assert navigation occurred
  });
});

Manual testing checklist

  • App launches successfully
  • All screens render correctly
  • Navigation between screens works
  • API calls succeed
  • Authentication flow works
  • Push notifications work
  • Deep linking works
  • Offline functionality works
  • Performance is acceptable
  • No console errors or warnings

Common Migration Issues

# Clear all caches
npx expo start --clear
rm -rf node_modules
npm install
# iOS
cd ios && pod install && cd ..
npx expo run:ios

# Android
cd android && ./gradlew clean && cd ..
npx expo run:android
# Update types
npm install --save-dev @types/react @types/react-native

# Restart TypeScript server in your editor
# Let Expo resolve versions
npx expo install --fix

# Or check for conflicts
npm ls expo

Migration Checklist

Before migration

  • Read the upgrade guide for target SDK version
  • Create a new branch in version control
  • Document current app behavior
  • Back up current codebase
  • Inform team about migration
  • Plan rollback strategy

During migration

  • Update Expo SDK version
  • Update all Expo packages
  • Update third-party dependencies
  • Address breaking changes
  • Update TypeScript types
  • Clear all caches
  • Rebuild native projects

After migration

  • Test all features
  • Run automated tests
  • Test on iOS and Android
  • Check performance metrics
  • Monitor crash reports
  • Update documentation
  • Notify team of completion

Getting Help

If you encounter issues: