This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Native project upgrade helper

Edit page

View file-by-file diffs of all the changes you need to make to your native projects to upgrade them to the next Expo SDK version.


If you manage your native projects (android and ios directories), to upgrade to the latest Expo SDK, you have to make changes to your native projects. It can be a complex process to find which native file changes and what to update in which file.

The following guide provides diffs to compare native project files between your project's current SDK version and the target SDK version you want to upgrade. You can use them to make changes to your project depending on the expo package version your project uses. The tools on this page are similar to React Native Upgrade Helper. However, they are oriented around projects that use Expo modules and related tooling.

Upgrade native project files

Once you have upgraded your Expo SDK version and related dependencies, use the diff tool below to learn about changes you need to make to your native project and bring them up to date with the current Expo SDK version.

Choose your from SDK version and to SDK version to see the generated diff. Then, apply those changes to your native projects by copying and pasting or manually making changes to the project files.

From SDK version:

To SDK version:

Native code changes from SDK 57 to 58

Upgrade your native project with an AI agent

Paste this into Claude, Cursor, Codex, or another agent. It carries the full diff from SDK 57 to SDK 58.

116116 def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
117117 shrinkResources enableShrinkResources.toBoolean()
118118 minifyEnabled enableMinifyInReleaseBuilds
119 proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
119 proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
120120 def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
121121 crunchPngs enablePngCrunchInRelease.toBoolean()
122122 }
88 dependencies {
99 classpath('com.android.tools.build:gradle')
1010 classpath('com.facebook.react:react-native-gradle-plugin')
11 classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
11 def kotlinVersion = findProperty('android.kotlinVersion')
12 classpath(kotlinVersion ? "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" : 'org.jetbrains.kotlin:kotlin-gradle-plugin')
1213 }
1314}
1415
1111*.iml
1212*.hprof
1313.cxx/
14gradle/gradle-daemon-jvm.properties
1415
1516# generated inline modules
1617app/src/main/java/inline/
2525# Enable AAPT2 PNG crunching
2626android.enablePngCrunchInReleaseBuilds=true
2727
28# Enable R8 in release builds to shrink, optimize and obfuscate the Java/Kotlin code.
29# The app is optimized using the `proguard-android-optimize.txt` preset.
30# Add custom keep rules to android/app/proguard-rules.pro.
31# See https://developer.android.com/topic/performance/app-optimization/enable-app-optimization
32android.enableMinifyInReleaseBuilds=true
33
2834# Use this property to specify which architecture you want to build.
2935# You can also override it from the CLI using
3036# ./gradlew <task> -PreactNativeArchitectures=x86_64
4652# Note: Only works with ReactActivity and should not be used with custom Activity.
4753edgeToEdgeEnabled=true
4854
49# Enable GIF support in React Native images (~200 B increase)
55# Enable GIF support in React Native's <Image> component.
56# Disable to reduce app size if you only render GIFs via expo-image.
57# See https://docs.expo.dev/distribution/app-size/
5058expo.gif.enabled=true
51# Enable webp support in React Native images (~85 KB increase)
59# Enable WebP support in React Native's <Image> component.
60# Disable to reduce app size if you only render WebP via expo-image.
61# See https://docs.expo.dev/distribution/app-size/
5262expo.webp.enabled=true
53# Enable animated webp support (~3.4 MB increase)
54# Disabled by default because iOS doesn't support animated webp
63# Enable animated WebP support in React Native's <Image> component.
64# Disabled by default because iOS doesn't support animated WebP.
5565expo.webp.animated=false
5666
5767# Enable network inspector
5969
6070# Use legacy packaging to compress native libraries in the resulting APK.
6171expo.useLegacyPackaging=false
72
73# Opt out of built-in kotlin and new DSL behavior that ships with AGP 9.
74# Starting from AGP 10.x these opt outs will be removed.
75android.builtInKotlin=false
76android.newDsl=false
11distributionBase=GRADLE_USER_HOME
22distributionPath=wrapper/dists
3distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
3distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
44networkTimeout=10000
55validateDistributionUrl=true
66zipStoreBase=GRADLE_USER_HOME
1111 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
1212 BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
1313 F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
14 F11748442D0307B40044C1D9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748432D0307B40044C1D9 /* SceneDelegate.swift */; };
1415/* End PBXBuildFile section */
1516
1617/* Begin PBXFileReference section */
2122 BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
2223 ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
2324 F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = HelloWorld/AppDelegate.swift; sourceTree = "<group>"; };
25 F11748432D0307B40044C1D9 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = HelloWorld/SceneDelegate.swift; sourceTree = "<group>"; };
2426 F11748442D0722820044C1D9 /* HelloWorld-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "HelloWorld-Bridging-Header.h"; path = "HelloWorld/HelloWorld-Bridging-Header.h"; sourceTree = "<group>"; };
2527/* End PBXFileReference section */
2628
3941 isa = PBXGroup;
4042 children = (
4143 F11748412D0307B40044C1D9 /* AppDelegate.swift */,
44 F11748432D0307B40044C1D9 /* SceneDelegate.swift */,
4245 F11748442D0722820044C1D9 /* HelloWorld-Bridging-Header.h */,
4346 BB2F792B24A3F905000567C9 /* Supporting */,
4447 13B07FB51A68108700A75B9A /* Images.xcassets */,
176179 );
177180 runOnlyForDeploymentPostprocessing = 0;
178181 shellPath = /bin/sh;
179 shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
182 shellScript = "if [[ -f \"$SRCROOT/.xcode.env\" ]]; then\n source \"$SRCROOT/.xcode.env\"\nfi\nif [[ -f \"$SRCROOT/.xcode.env.local\" ]]; then\n source \"$SRCROOT/.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$SRCROOT/.xcode.env.updates\" ]]; then\n source \"$SRCROOT/.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$SRCROOT/.xcode.env.local\" ]]; then\n source \"$SRCROOT/.xcode.env.local\"\nfi\n\n\"`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\"\n\n";
180183 };
181184 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
182185 isa = PBXShellScriptBuildPhase;
230233 buildActionMask = 2147483647;
231234 files = (
232235 F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
236 F11748442D0307B40044C1D9 /* SceneDelegate.swift in Sources */,
233237 );
234238 runOnlyForDeploymentPostprocessing = 0;
235239 };
33import ReactAppDependencyProvider
44
55@main
6class AppDelegate: ExpoAppDelegate {
6class AppDelegate: ExpoAppDelegate, ExpoReactNativeFactoryProvider {
77 var window: UIWindow?
88
99 var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
2020 reactNativeDelegate = delegate
2121 reactNativeFactory = factory
2222
23#if os(iOS) || os(tvOS)
24 window = UIWindow(frame: UIScreen.main.bounds)
25 factory.startReactNative(
26 withModuleName: "main",
27 in: window,
28 launchOptions: launchOptions)
29#endif
30
23 // The window is created and React Native is started by `SceneDelegate` under the
24 // scene-based life cycle (required by the iOS 27 SDK).
3125 return super.application(application, didFinishLaunchingWithOptions: launchOptions)
3226 }
33
34 // Linking API
35 public override func application(
36 _ app: UIApplication,
37 open url: URL,
38 options: [UIApplication.OpenURLOptionsKey: Any] = [:]
39 ) -> Bool {
40 return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
41 }
42
43 // Universal Links
44 public override func application(
45 _ application: UIApplication,
46 continue userActivity: NSUserActivity,
47 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
48 ) -> Bool {
49 let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
50 return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
51 }
5227}
5328
5429class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
3333 <key>NSAllowsLocalNetworking</key>
3434 <true/>
3535 </dict>
36 <key>UIApplicationSceneManifest</key>
37 <dict>
38 <key>UIApplicationSupportsMultipleScenes</key>
39 <false/>
40 <key>UISceneConfigurations</key>
41 <dict>
42 <key>UIWindowSceneSessionRoleApplication</key>
43 <array>
44 <dict>
45 <key>UISceneConfigurationName</key>
46 <string>Default Configuration</string>
47 <key>UISceneDelegateClassName</key>
48 <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
49 </dict>
50 </array>
51 </dict>
52 </dict>
3653 <key>UILaunchStoryboardName</key>
3754 <string>SplashScreen</string>
3855 <key>UIRequiredDeviceCapabilities</key>
1internal import Expo
2
3@objc(SceneDelegate)
4class SceneDelegate: ExpoAppSceneDelegate {
5 // Extension point for config plugins.
6}
package.json
MODIFIED
22 "name": "expo-template-bare-minimum",
33 "description": "This bare project template includes a minimal setup for using unimodules with React Native.",
44 "license": "0BSD",
5 "version": "57.0.32",
5 "version": "58.0.25",
6 "repository": {
7 "type": "git",
8 "url": "https://github.com/expo/expo.git",
9 "directory": "templates/expo-template-bare-minimum"
10 },
611 "main": "index.js",
712 "scripts": {
813 "start": "expo start --dev-client",
1116 "web": "expo start --web"
1217 },
1318 "dependencies": {
14 "expo": "~57.0.30",
15 "expo-status-bar": "~57.0.1",
16 "react": "19.2.3",
17 "react-native": "0.86.3"
19 "expo": "~58.0.16",
20 "expo-status-bar": "~58.0.3",
21 "react": "19.3.0",
22 "react-native": "0.88.0"
1823 },
1924 "publishConfig": {
2025 "executableFiles": [
2126