+1 (415) 843-4662

Cordova → Capacitor migration checklist (2026 edition)

Cordova is in maintenance mode and Capacitor has been Ionic's official native runtime since 2019, but in 2026 we still meet plenty of Ionic apps running on Cordova. The migration is well documented by the Capacitor team; what teams lack is a checklist that tells them the order, and the places where the official guide assumes a cleaner project than the one they have. This is ours, updated for Capacitor 8.

0. Prerequisites

  • Node 22 or later (Capacitor 8 requires it)
  • Xcode 26 and a current Android Studio; check the Capacitor 8 upgrade guide for the exact minimums
  • Your Ionic app builds and runs in a browser with ionic serve — if it does not, fix that first; Capacitor cannot fix an app that does not build
  • A green test suite, or at least a smoke-test script you can run on device before and after

1. Plugin audit table

Every migration starts with a table. List every cordova-plugin-* in package.json and config.xml, then decide its fate:

Cordova pluginDecisionCapacitor replacement
cordova-plugin-cameraReplace@capacitor/camera
cordova-plugin-fileReplace@capacitor/filesystem
cordova-plugin-splashscreenReplace@capacitor/splash-screen
cordova-plugin-statusbarReplace@capacitor/status-bar
cordova-plugin-deviceReplace@capacitor/device
cordova-plugin-network-informationReplace@capacitor/network
cordova-plugin-inappbrowserReplace@capacitor/browser (for external URLs)
phonegap-plugin-pushReplace@capacitor/push-notifications
cordova-plugin-whitelistRemoveNot needed; Capacitor handles origins
cordova-plugin-ionic-webviewRemoveBuilt into Capacitor
cordova-plugin-ionic (Appflow)RemoveSee our Appflow migration guide
cordova-plugin-x-socialsharingReplace@capacitor/share
cordova-sqlite-storageReplace@capacitor-community/sqlite
Vendor SDK plugin with no equivalentKeep (for now)Load as a Cordova plugin under Capacitor, then rewrite

Three columns matter: Replace with an official or Capawesome/community plugin, Remove because Capacitor covers it, or Keep as a Cordova plugin loaded by Capacitor. Capacitor runs most Cordova plugins unchanged, but plugins that modify the Cordova config.xml at install time, depend on cordova-plugin-ionic-webview, or hook into Cordova's build process will not work and should be flagged as "Keep — verify on device" before you trust them.

2. Install Capacitor and create the config

npm install @capacitor/core @capacitor/cli@latest
npx cap init "My App" com.example.myapp --web-dir www

Use the same appId as the Cordova widget id; changing it creates a new app in the stores. Then add the platforms:

npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android

Capacitor 8 creates the iOS project with Swift Package Manager by default. If a kept Cordova plugin or a vendor SDK only ships CocoaPods, add iOS with npx cap add ios --packagemanager CocoaPods instead.

3. config.xml → capacitor.config.ts mapping

Most of config.xml has no Capacitor equivalent because the native projects are now yours to edit directly. The pieces that do map:

config.xmlCapacitor
<widget id>appId in capacitor.config.ts
<name>appName
<content src="index.html">webDir (the built output folder)
<preference name="SplashScreenDelay">plugins.SplashScreen.launchShowDuration
<preference name="StatusBarStyle">StatusBar.setStyle() at runtime
<preference name="Orientation">Xcode / AndroidManifest.xml directly
<allow-navigation href>server.allowNavigation
<access origin>Usually unnecessary; CORS is a server concern
<icon> / <splash>@capacitor/assets generates native assets
<plugin ... variable=>Plugin-specific plugins.<Name> block, or variables.gradle / Info.plist

A representative result:

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.myapp',
  appName: 'My App',
  webDir: 'www',
  plugins: {
    SplashScreen: {
      launchShowDuration: 0,
      launchAutoHide: false,
    },
  },
};

export default config;

4. Platform folder hygiene

Under Cordova, platforms/ and plugins/ were generated and ignored. Under Capacitor, ios/ and android/ are source — committed, edited in Xcode and Android Studio, and never regenerated casually.

  • Delete platforms/, plugins/, hooks/, config.xml, and www/ build leftovers from the repo
  • Remove the old ionic.config.json integrations.cordova block
  • Add ios/App/Pods, ios/App/build, android/.gradle, android/build, android/app/build to .gitignore; keep the rest of ios/ and android/
  • Move your Cordova resources/ icons and splashes to assets/ and run npx @capacitor/assets generate
  • Port any config.xml <edit-config> and <config-file> entries by hand into Info.plist and AndroidManifest.xml — permissions strings (NSCameraUsageDescription and friends) are the usual ones

5. Code changes

Replace deviceready with nothing — Capacitor plugins are available as soon as the web app loads. Replace window.cordova.* calls with plugin imports:

// Before (Cordova, via ionic-native / awesome-cordova-plugins)
// this.camera.getPicture({ destinationType: this.camera.DestinationType.DATA_URL })

// After (Capacitor)
import { Camera, CameraResultType } from '@capacitor/camera';

const photo = await Camera.getPhoto({
  resultType: CameraResultType.Uri,
  quality: 80,
});
// photo.webPath is safe to bind to <img src>; photo.path is the native path

Search for cordova.file., file://, and cdvfile:// paths — file handling is the largest source of post-migration bugs (see gotchas below).

6. CI changes

  • Replace ionic cordova build ios / android with npm run build && npx cap sync followed by xcodebuild / gradlew — or a fastlane lane that does both
  • Cache node_modules, ~/.gradle, and (if used) CocoaPods
  • Add npx cap sync to the pipeline immediately after the web build; the commonest CI failure after migration is a stale native project
  • If you used Appflow for builds, this is the moment to move to GitHub Actions, Codemagic, or Bitrise — see our post-Appflow CI/CD tutorial

7. Gotchas we hit on almost every migration

  • Splash screen flashes then hides too early or never. Set launchAutoHide: false and call SplashScreen.hide() yourself once the first screen has rendered. Cordova's AutoHideSplashScreen preference no longer applies.
  • Status bar overlaps content on Android 15+. Capacitor 8 moved edge-to-edge handling to the System Bars plugin and CSS env() variables; the old adjustMarginsForEdgeToEdge setting is gone. Budget an afternoon for layout checks on a modern Android device.
  • File paths. cordova.file.dataDirectory becomes Directory.Data with @capacitor/filesystem, and a native path must be converted with Capacitor.convertFileSrc() before it can be loaded in the WebView. Stored absolute paths from the Cordova era will be wrong on iOS after the app container moves — store relative paths and a directory enum.
  • window.open and external links. Under Cordova, InAppBrowser intercepted them; under Capacitor, use @capacitor/browser for external URLs and check server.allowNavigation if you rely on navigating the WebView to another origin.
  • Keyboard behaviour. Cordova's KeyboardResize preferences map to plugins.Keyboard.resize in capacitor.config.ts; test every form on iOS.
  • Plugins that "work" until release. A kept Cordova plugin may rely on a config.xml preference you no longer have. Test release builds, not just debug.

8. Sign-off

Before the store submission that ships the migrated app:

  • Smoke test the full plugin audit table on a physical iOS and Android device
  • Verify push registration, deep links, and file uploads end to end
  • Confirm the appId, version and build numbers match the store listing
  • Tag the last Cordova commit so you can diff against it for the next six months

Need a second pair of hands or a plugin with no Capacitor equivalent rewritten natively? Our Capacitor consultants do this every month.