Upgrades · Tooling

Stop using the React Native upgrade helper blind

Published April 22, 2026 · 9 minute read
TL;DR

The React Native upgrade helper diffs a tutorial app. Your app isn't a tutorial app.

Its output is a starting point. Treat it as an answer and you'll ship a build that compiles but doesn't work.

Read every hunk. Apply what maps to your code. Skip what doesn't. Then do the work the helper can't see — native modules, forked libraries, custom Gradle, custom Pods.

The React Native upgrade helper is the first thing everyone opens when they start an upgrade. Pick a from version. Pick a to version. Stare at a wall of diffs across package.json, Podfile, build.gradle, AppDelegate, MainApplication, Metro config, and about twenty other files.

Most teams then copy the diff into their repo.

That's the mistake.

The helper doesn't know what your app looks like. It shows the diff between two versions of the project template — the thing npx react-native init would have produced if you'd run it today versus back when you started. Your app forked from that template on day one. Every custom native module, every extra CocoaPod, every Gradle task someone added to fix an obscure signing bug in 2022 is invisible to the helper.

Apply the diff as if it were a patch and you'll ship a build that compiles, boots, and then falls over the first time it hits the code the helper couldn't see.

On this page
  1. What the upgrade helper actually is
  2. What it gets right
  3. What it misses
  4. How to use it correctly
  5. After you apply the diff
  6. When the helper is useless
  7. FAQ

What the upgrade helper actually is

Two sentences: it's a web app that renders the file-level diff between two versions of the React Native project template. The template is the scaffolding npx react-native init generates — a Hello World app with iOS and Android wrappers.

There's a CLI equivalent — npx @react-native-community/cli upgrade — that applies the same patch to a local checkout. Same content. Different delivery.

Nothing about the helper reads your codebase. It doesn't know your React Native version is a fork. It doesn't know you have a forked copy of react-native-firebase vendored into a monorepo. It doesn't know your iOS deployment target is pinned because of a legacy push-notification framework. It's a diff between two static trees.

The helper is useful for the changes that were genuinely in the template and haven't been customized in your app:

If your app is close to the template — small, recent, few native dependencies — the helper's diff is maybe 85% correct as a direct patch. For those apps, npx @react-native-community/cli upgrade will get you most of the way in one command.

Most apps aren't close to the template.

What it misses

Every real app has code the helper can't see. The categories below come up on every rescue we take on.

Your native dependencies

The helper shows you what React Native and the template demand. It does not show you what your app's native dependencies demand. Going from React Native 0.72 to 0.74 might be a clean template diff. It might also require you to bump react-native-reanimated, react-native-gesture-handler, react-native-screens, and react-native-firebase to specific versions that match your target — none of which appear in the helper.

Those bumps often carry their own native config changes. New Podfile entries. Gradle dependencies additions. Manifest permission changes. None of it is in the helper's diff.

Your custom native modules

If your team wrote native code — a custom RNMyModule.swift, an Android bridge for a third-party SDK — that code never existed in the template. The helper has nothing to say about it. Every React Native version bump risks breaking that code's API contract with the bridge, and you'll only find out at runtime.

Your build configuration

Custom Gradle flavors. Custom signing configs. Multi-target Xcode projects. Build phases added for Firebase, Sentry, or a CI script. CocoaPods pinned to forks. Gradle plugins from third parties. Every one of these is a landmine when the helper's diff touches a file they overlap with.

The helper will happily show you a change to android/app/build.gradle that erases your custom productFlavors block because the template doesn't have one. You have to catch it.

Breaking changes in React Native itself

The helper shows file diffs. It doesn't show semantic breaking changes in the JavaScript API. PushNotificationIOS moved out of core. Clipboard moved out of core. ViewPropTypes is gone. Various components got deprecated. Your app references those, and your app breaks the moment the version updates — but the helper diff won't tell you which lines to change.

Read the React Native release notes for every version between your from and your to. The API changes are in the prose, not the template.

New Architecture and Hermes

The helper shows you the flags. It doesn't tell you whether your dependency tree is compatible with flipping them. Fabric and TurboModules break older libraries in ways that don't show up as a compile error — they show up as a silently broken component in production. If your upgrade crosses the New Architecture threshold, the flag change is the easy part. Validating every native library against it is the work.

How to use it correctly

A four-step recipe. Follow it in order.

1. Render the diff one minor version at a time

Going from 0.70 to 0.76 in one diff is a category error. The diff collapses the reasoning — you can't tell which changes came in at 0.71, which at 0.73, which at 0.75. Debugging the combined result is much harder than debugging six separate jumps.

Render 0.70 → 0.71. Apply. Verify. Then 0.71 → 0.72. Repeat. Boring, but every version hop tells you what broke and why, in isolation.

2. Review every hunk before applying

Do not run npx @react-native-community/cli upgrade and accept the merges without reading them. Use the web helper, open your file, and compare side by side. For each hunk, ask:

Apply mandatory changes. Skip cosmetic ones. Reconcile anything that conflicts with your customizations by hand.

3. Bump native dependencies the helper didn't touch

After applying the helper's diff, the helper is done. You aren't. Now open package.json and bump react-native-reanimated, react-native-screens, react-native-gesture-handler, navigation, Firebase, any other native-heavy dependency, to versions compatible with the React Native version you just landed on. Read each one's changelog. Each will probably have its own native config changes.

4. Verify the native build after every step

After the helper's diff lands, after every native dependency bump, run xcodebuild and ./gradlew assembleDebug. Not npm install. Not tsc. The actual native builds. If either fails, fix it before the next step. Stacking unresolved failures is how upgrades become rescues.

If you want a checklist for the audit step that precedes all of this, the full React Native upgrade playbook covers audit, plan, execute, and verify in more detail. The upgrade helper only addresses the execute phase — and only part of it.

After you apply the diff

Most silent regressions land here. The build is green. The app boots. Something is wrong and no one notices for a week.

Post-diff verification checklist

When the helper is useless

Three scenarios where the helper is close to worthless and teams keep reaching for it anyway.

Expo managed workflow

The managed workflow abstracts the native projects. You don't own ios/ or android/. Upgrading means bumping the Expo SDK version, which sets the React Native version for you. The upgrade helper's diff touches files you don't have. Read the Expo SDK release notes instead — that's where the actual change list lives.

Heavily forked native code

If your app has a vendored fork of React Native itself — because you needed a patch that wasn't upstream, or you're supporting a non-standard platform — the helper's diff is against upstream, not your fork. Applying it will undo whatever your fork changed. Use it as a reference. Don't use it as a patch.

Brownfield integrations

React Native embedded inside a larger native iOS or Android app. The template assumes React Native is the app. Brownfield apps have their own AppDelegate, their own MainApplication, their own bridge lifecycle. The helper's changes to those files almost always don't apply. Read the upstream release notes for the bridge-initialization changes and apply them by hand to your host app.

Rule of thumb

If your app looks like the template, the helper is a patch. If your app doesn't look like the template, the helper is a reading assignment.

Not sure where your upgrade risk is?

Run the free scanner against your lockfile. You'll get a ranked list of known CVEs and outdated packages in about 90 seconds — a useful starting input before you open the upgrade helper.

Frequently Asked Questions

What is the React Native upgrade helper?

A community-maintained web tool at react-native-community.github.io/upgrade-helper that shows the diff between two versions of the React Native project template. You pick a from version and a to version, and it renders every file that changed in the boilerplate — package.json, Podfile, build.gradle, AppDelegate, MainApplication, Metro config, and so on. It's the canonical starting point for any version jump. It is not a patch for your app.

Do I need to apply every change in the upgrade helper diff?

No. The diff shows every change to the template. Your app probably doesn't match the template. Some changes are mandatory — new Gradle plugin versions, iOS deployment target bumps, New Architecture flags if you're opting in. Others are cosmetic, or apply to files your app customized years ago. Review every hunk. Apply only what maps to something real in your codebase.

Does the React Native upgrade helper work with Expo?

Partially. If you're on the managed workflow, the helper is mostly irrelevant — Expo handles the native config for you, and you upgrade by changing the Expo SDK version. If you're on the bare workflow or have prebuild output checked in, the helper still applies to the native projects. But cross-check every change against the Expo SDK release notes for that version — some config keys are Expo-specific.

Why does my app still break after I apply the upgrade helper's diff?

Because the helper diffs the template. Your app isn't the template. It has custom native modules, forked libraries, custom Gradle tasks, extra CocoaPods, entitlements, build phases, and flavors the template never saw. Applying the diff gets you maybe 70% of the way. The last 30% is the part you can't copy from a generator — it requires reading every native dependency's changelog and reconciling it against your actual build config.

Is there a CLI version of the React Native upgrade helper?

Yes. npx @react-native-community/cli upgrade applies the same patch set locally. It's faster than copy-pasting from the web diff, and it handles merge conflicts in your files interactively. On any non-trivial app, plan on reviewing and reverting some of its edits — the CLI is just as template-naive as the web version.

Can the upgrade helper span multiple versions at once?

It can render a diff between any two versions it supports. That doesn't mean you should apply it in one step. A multi-minor diff collapses the reasoning about why each change happened, which makes debugging the inevitable breakage much harder. Use it one minor version at a time. Staircase the upgrade, don't leap.