packages/migrate/docs/v9.md
This page covers what npx @ionic/migrate handles for the Ionic 8 to Ionic 9
upgrade, and what it leaves to you. The full list of breaking changes, with
before/after examples, is in the
Ionic 9 upgrade guide. Every
finding the tool prints links to the section of that guide it came from.
Find your framework below, then read the
all frameworks section as well. Those changes come from
@ionic/core, so they apply on top of the framework-specific ones, and they are
the whole list for a vanilla app.
experimental migrations only run with --experimental.
| Change | Mode |
|---|---|
@ionic/angular -> @ionic/angular/lazy, /standalone -> @ionic/angular | auto |
@ionic/angular package bump | auto |
moduleResolution: "node" -> "bundler" in tsconfig*.json | auto |
| TypeScript raised to the 5.4 floor | auto |
CSS ~ prefix removal in @ionic/angular imports | auto |
Add provideZoneChangeDetection() to a standalone bootstrap (keep Zone.js) | auto |
| NgModule bootstrap zone provider | report |
IonicModule deprecation (provideIonicAngular()) | report |
| Angular below the 18 floor | report |
Angular 22's OnPush default and its Node floor | report |
@ionic/angular-toolkit version bump | report |
browserslist entries below Angular 20+'s own browser policy | report |
browserslist entries raised to Angular's policy floors | experimental |
| Change | Mode |
|---|---|
@ionic/react + React 18 + React Router v6 bumps, drop @types/react-router* | auto |
| TypeScript raised to the 5.4 floor | auto |
<Route exact> removal, component={X} -> element={<X />} | auto |
React Router v6: removed imports, IonRedirect, render/non-identifier component, route children, history prop, regex paths | report |
useIonModal/useIonPopover calls whose componentProps no longer type check | report |
| An inline overlay component's unannotated props parameter | experimental |
| Change | Mode |
|---|---|
@ionic/vue + Vue Router 5 + Vue 3.5 bumps | auto |
next() in navigation guards | report |
| Change | Mode |
|---|---|
@ionic/core package bump | auto |
autocorrect="off" on ion-input/ion-searchbar | auto |
browserslist entries raised to the v9 browser floors | auto |
Browsers from the v9 list a browserslist doesn't name | report |
Legacy picker (ion-picker-legacy, pickerController, removed types) | report |
ion-img deprecation | report |
ion-nav router removal (setRouteId/getRouteId/updateURL) | report |
@ionic/core imports outside the new exports allowlist | report |
| Capacitor below version 7 (Ionic 9's minimum) and the Capacitor 2 native-detection change | report |
ion-input/ion-textarea/ion-select internal DOM and shadow part changes | report |
label-placement="floating" with slotted start/end content | report |
ion-textarea md min-height 56px -> 72px | report |
ion-modal handleBehavior default ("none" -> "cycle") | report |
ion-select ionChange firing and the action sheet selected role | report |
swipeBackEnabled config, now read once at outlet mount | report |
The tool can't point at the code these changes affect, so check them against the upgrade guide yourself:
/* suffix on any route whose element contains nested
routes or a child IonRouterOutlet (path="/tabs" -> path="/tabs/*").
Knowing which routes those are means resolving each element back to what it
renders, so flagging every route without a suffix would be noise.OnPush, so state mutated as a plain field
in an Ionic lifecycle hook stops re-rendering. The report flags Angular 22 in
package.json, but it doesn't find the affected components - ng update has a
migration for that.tsconfig.json, so a component behind a paths alias
(@/components/Modal), or one typed as React.FC with @types/react
uninstalled, is skipped rather than guessed at, and a build error can still be
waiting in one. A mismatch that only exists under strict is skipped too, so
that everything it does report is a real error.bootstrapApplication
shape. NgModule apps are flagged for manual migration instead. Neither fires
unless the app loads Zone.js, since there is nothing to preserve otherwise and
the provider fails to bootstrap without it.--experimental annotates from the
componentProps object literal it is passed. That is a guess at intent
({ name: 'Dave' } gives { name: string }), and it declines anything but a
literal, or props that don't cover every name the component reads.ion-select's part="inner" has
none at all.browserslist list is read from .browserslistrc, a browserslist file, or
the package.json field. A query-style list (last 2 versions, > 0.5%)
names no browser, so nothing in it is raised or reported.@angular/build and resolved with the project's
browserslist. Without node_modules the report names the policy but not the
versions. The two things it reads are Angular internals (a .browserslistrc in
the package on 20, a BASELINE_DATE constant on 21+), so when a new Angular
major lands, check the reported floors against the ng build warning before
trusting them.moduleResolution fix skips a tsconfig whose module is CommonJS.
TypeScript rejects bundler resolution there, and a Node-side config doesn't
resolve @ionic/angular subpaths anyway.