Skip to main content
Version: v9

Breaking changes in code

Android

The @NativePlugin annotation and several deprecated permission and activity-result APIs from that era have been removed. For the overall migration pattern, see the Capacitor 3 plugin migration guide.

RemovedReplacement
@NativePlugin annotation@CapacitorPlugin
Plugin.saveCall(PluginCall)Bridge.saveCall(PluginCall) or PluginCall.setKeepAlive(true)
Plugin.getSavedCall() (no-arg)Bridge.getSavedCall(String)
Plugin.freeSavedCall()PluginCall.release(Bridge)
Plugin.hasDefinedPermissions(String[])Plugin.isPermissionDeclared(String)
Plugin.hasPermission(String)Plugin.getPermissionState(String) / getPermissionStates(), or ActivityCompat.checkSelfPermission(Context, String)
Plugin.pluginRequestPermission(String, int)Plugin.requestPermissionForAlias(...) with @PermissionCallback
Plugin.pluginRequestPermissions(String[], int)Plugin.requestPermissionForAliases(...) with @PermissionCallback
Plugin.pluginRequestAllPermissions()Plugin.requestAllPermissions(PluginCall, String) with @PermissionCallback
Plugin.startActivityForResult(PluginCall, Intent, int)Plugin.startActivityForResult(PluginCall, Intent, String) with @ActivityCallback
Bridge.startActivityForPluginWithResult(PluginCall, Intent, int)Plugin.startActivityForResult(PluginCall, Intent, String) with @ActivityCallback

Capacitor 9 also removes the remaining Java APIs that were deprecated in previous major versions. If your plugin still uses any of them, replace them as follows:

RemovedReplacement
CapConfig(AssetManager, JSONObject) constructorCapConfig.loadDefault(Context) to load from capacitor.config.json, or CapConfig.Builder for embedded use
CapConfig.getObject(String), getString(...), getBoolean(...), getInt(...), getArray(...)The typed getters on CapConfig for main config values and the PluginConfig accessors for plugin config values
PluginCall.save()setKeepAlive(true)
PluginCall.isSaved()isKeptAlive()
PluginCall.hasOption(String)Typed accessors (getString(...), getInt(...), etc.)
PluginCall.isReleased()No replacement, released calls are managed by the bridge
Bridge.CAPACITOR_HTTPS_INTERCEPTOR_STARTCAPACITOR_HTTP_INTERCEPTOR_START, all proxied requests are handled by it
Plugin.getConfigValue(String)getConfig() and the typed accessors on PluginConfig
MessageHandler(Bridge, WebView, Object) constructorMessageHandler(Bridge, WebView)
WebViewLocalServer.PathHandler.getResponseHeaders()buildDefaultResponseHeaders()

iOS

Capacitor 9 removes the Swift and Objective-C APIs that were deprecated in previous major versions. If your plugin still uses any of them, replace them as follows.

CAPBridge compatibility class removed

The CAPBridge class was a compatibility shim and has been removed entirely. Use the replacements below:

RemovedReplacement
CAPBridge.statusBarTappedNotificationNotification.Name.capacitorStatusBarTapped
CAPBridge.getLastUrl()ApplicationDelegateProxy.shared.lastURL
CAPBridge.handleOpenUrl(_:_:)ApplicationDelegateProxy.shared.application(_:open:options:)
CAPBridge.handleContinueActivity(_:_:)ApplicationDelegateProxy.shared.application(_:continue:restorationHandler:)
CAPBridge.handleAppBecameActive(_:)No longer needed, it was a no-op

Bridge (CAPBridgeProtocol) methods removed

RemovedReplacement
getWebView()webView property
isSimulator()isSimEnvironment property
isDevMode()isDevEnvironment property
getStatusBarVisible() / setStatusBarVisible(_:)statusBarVisible property
getStatusBarStyle() / setStatusBarStyle(_:)statusBarStyle property
setStatusBarAnimation(_:)statusBarAnimation property
getUserInterfaceStyle()userInterfaceStyle property
getLocalUrl()config.localURL
getSavedCall(_:)savedCall(withID:)
releaseCall(callbackId:)releaseCall(withID:)
presentVC(_:animated:completion:)viewController?.present(_:animated:completion:)
dismissVC(animated:completion:)viewController?.dismiss(animated:completion:)
modulePrint(_:_:)CAPLog.print(_:)

Other removals

RemovedReplacement
CAPNotifications enumNotification.Name.capacitor* constants (e.g. Notification.Name.capacitorOpenURL)
PluginCallErrorData, PluginResultData and JSResultBody typealiasesPluginCallResultData
CAPPluginCall.hasOption(_:)Typed accessors (getString(_:), getInt(_:), etc.)
JSDate.toString(_:)No longer needed, dates are mapped to strings during serialization
InstanceConfiguration.getPluginConfigValue(_:_:)getPluginConfig(_:)
InstanceConfiguration.getValue(_:) / getString(_:)Direct property accessors on InstanceConfiguration
CAPPlugin.getConfigValue(_:)getConfig() and the typed accessors on PluginConfig
CAPFileManager.getPortablePath(host:uri:)portablePath(fromLocalURL:) on the bridge
CapacitorBridge initializer taking cordovaConfigurationThe initializer without the cordovaConfiguration parameter
CapacitorBridge.httpsInterceptorStartIdentifierhttpInterceptorStartIdentifier, all proxied requests are handled by it
CapacitorUrlRequest.setRequestHeaders([String: String])setRequestHeaders([String: Any]). Note: the replacement sets header values instead of appending them, so repeated keys overwrite the previous value

Updating Capacitor to 9.0 in your plugin

note

This guide covers plugin-author changes. If you're updating an app, see Updating to 9.0 instead.

note

Capacitor 9 hasn't reached general availability yet. Dependency versions below point at the next dist-tag / current prerelease; once 9.0.0 ships, use a normal ^9.0.0 range instead.

Using @capacitor/plugin-migration-v8-to-v9

From the plugin folder, run npx @capacitor/plugin-migration-v8-to-v9@latest and it will perform most of the file changes automatically.

Updating the files manually

Updating Capacitor dependencies

Update @capacitor/cli, @capacitor/core, @capacitor/android and @capacitor/ios in devDependencies to next. Update @capacitor/core in peerDependencies to >=9.0.0-alpha.6 (check npm for the current prerelease, since it moves forward regularly).

Cordova support is now optional

Capacitor's Cordova compatibility layer is now only wired into a consuming app when that app actually has a Cordova plugin installed, on both Android and iOS. If your plugin's own native code directly references symbols from Capacitor's Cordova compatibility layer (for example com.getcapacitor.cordova.CordovaPlugin on Android), be aware that layer may not be present in a consuming app that has no Cordova plugins. There is currently no configuration option to force it to be included.

If your plugin is SPM compatible, also remove the unconditional Cordova product dependency from your Package.swift — it's no longer guaranteed to be present in every consumer:

    dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
- .product(name: "Cordova", package: "capacitor-swift-pm"),
]

Update Android Plugin Variables

In your build.gradle file, update the following package versions:

ext {
// Note: Some of the following dependencies are optional - only add/update the ones your plugin actually uses.
// If you use any of these dependencies, update them to the versions shown below.
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
- androidxActivityVersion = project.hasProperty('androidxActivityVersion') ? rootProject.ext.androidxActivityVersion : '1.11.0'
+ androidxActivityVersion = project.hasProperty('androidxActivityVersion') ? rootProject.ext.androidxActivityVersion : '1.13.0'
androidxCoordinatorLayoutVersion = project.hasProperty('androidxCoordinatorLayoutVersion') ? rootProject.ext.androidxCoordinatorLayoutVersion : '1.3.0'
- androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.17.0'
+ androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.19.0'
androidxFragmentVersion = project.hasProperty('androidxFragmentVersion') ? rootProject.ext.androidxFragmentVersion : '1.8.9'
firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '25.0.1'
- playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '21.3.0'
+ playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '21.4.0'
- androidxBrowserVersion = project.hasProperty('androidxBrowserVersion') ? rootProject.ext.androidxBrowserVersion : '1.9.0'
+ androidxBrowserVersion = project.hasProperty('androidxBrowserVersion') ? rootProject.ext.androidxBrowserVersion : '1.10.0'
- androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.13.0'
+ androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.14.0'
- androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.4.1'
+ androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.4.2'
coreSplashScreenVersion = project.hasProperty('coreSplashScreenVersion') ? rootProject.ext.coreSplashScreenVersion : '1.2.0'
- androidxWebkitVersion = project.hasProperty('androidxWebkitVersion') ? rootProject.ext.androidxWebkitVersion : '1.14.0'
+ androidxWebkitVersion = project.hasProperty('androidxWebkitVersion') ? rootProject.ext.androidxWebkitVersion : '1.16.0'
- googleMapsPlayServicesVersion = project.hasProperty('googleMapsPlayServicesVersion') ? rootProject.ext.googleMapsPlayServicesVersion : '19.2.0'
+ googleMapsPlayServicesVersion = project.hasProperty('googleMapsPlayServicesVersion') ? rootProject.ext.googleMapsPlayServicesVersion : '20.0.0'
- googleMapsUtilsVersion = project.hasProperty('googleMapsUtilsVersion') ? rootProject.ext.googleMapsUtilsVersion : '3.19.1'
+ googleMapsUtilsVersion = project.hasProperty('googleMapsUtilsVersion') ? rootProject.ext.googleMapsUtilsVersion : '5.0.0'
- googleMapsKtxVersion = project.hasProperty('googleMapsKtxVersion') ? rootProject.ext.googleMapsKtxVersion : '5.2.1'
- googleMapsUtilsKtxVersion = project.hasProperty('googleMapsUtilsKtxVersion') ? rootProject.ext.googleMapsUtilsKtxVersion : '5.2.1'
+ googleMapsKtxVersion = project.hasProperty('googleMapsKtxVersion') ? rootProject.ext.googleMapsKtxVersion : '6.0.1'
+ googleMapsUtilsKtxVersion = project.hasProperty('googleMapsUtilsKtxVersion') ? rootProject.ext.googleMapsUtilsKtxVersion : '6.0.1'
- kotlinxCoroutinesVersion = project.hasProperty('kotlinxCoroutinesVersion') ? rootProject.ext.kotlinxCoroutinesVersion : '1.10.2'
+ kotlinxCoroutinesVersion = project.hasProperty('kotlinxCoroutinesVersion') ? rootProject.ext.kotlinxCoroutinesVersion : '1.11.0'
}

googleMapsUtilsVersion 5.0.0 includes upstream breaking changes to the Google Maps Utils API; check your usage against the Google Maps Android Utility Library release notes if your plugin depends on it directly.

Migrate core-ktx to core

androidx.core:core 1.19.0 merges every extension function previously shipped in core-ktx into core itself, turning core-ktx into an empty compatibility artifact. Drop the separate androidxCoreKTXVersion variable (reuse androidxCoreVersion instead) and depend on core rather than core-ktx:

dependencies {
- implementation "androidx.core:core-ktx:$androidxCoreVersion"
+ implementation "androidx.core:core:$androidxCoreVersion"
}

Remove targetSdkVersion, update minSdk / compileSdk

AGP 9 infers targetSdkVersion from compileSdkVersion when it isn't set, and on a library module (which is what your plugin's android/build.gradle is) targetSdkVersion has no runtime effect anyway, so drop it entirely:

# build.gradle

android {
- compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ minSdkVersion = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 26
- targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
}
}

Rename the default ProGuard file

AGP 9 removed proguard-android.txt — any build.gradle that still references it fails at Gradle configuration time, even with minifyEnabled false. Most official plugins already switched to proguard-android-optimize.txt in Capacitor 8, but if yours (or a fork of one) hasn't yet:

buildTypes {
release {
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

Remove the standalone Kotlin plugin

AGP 9 bundles the Kotlin Gradle Plugin natively (Kotlin 2.2.10) instead of requiring it to be applied separately. If your plugin still applies kotlin-android (or org.jetbrains.kotlin.android) and declares its own kotlin_version/kotlin-stdlib/kotlin-gradle-plugin, remove them, otherwise you'll hit a duplicate-plugin build failure:

buildscript {
- ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:9.2.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
-apply plugin: 'kotlin-android'

dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

Give your plugin a unique Android namespace

AGP 9 defaults android.uniquePackageNames to true: if your plugin's Android namespace collides with another library module in the same app, the build now fails instead of silently working. The most common cause is a leftover scaffold default (create-capacitor-plugin pre-fills the namespace prompt with com.mycompany.plugins.example, and pressing Enter accepts it). A fork that kept the original plugin's namespace unchanged can cause the same failure. Make sure your namespace in build.gradle is unique to your plugin.

Remove jcenter()

Gradle 9 fully removes the jcenter() repository helper (redirected to Maven Central since 2024). Any build.gradle that still calls it fails at Gradle configuration time:

repositories {
google()
- jcenter()
+ mavenCentral()
}

Update gradle plugin to 9.2.1

    dependencies {
- classpath 'com.android.tools.build:gradle:8.13.0'
+ classpath 'com.android.tools.build:gradle:9.2.1'
}

Update gradle wrapper to 9.5.1

# gradle-wrapper.properties

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
- distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
+ distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Update google services plugin

# build.gradle

dependencies {
classpath 'com.android.tools.build:gradle:9.2.1'
- classpath 'com.google.gms:google-services:4.4.4'
+ classpath 'com.google.gms:google-services:4.5.0'

Raise iOS Deployment Target to 16

If your plugin observes app lifecycle or URL-opening notifications, also check it against the iOS UIScene lifecycle adopted in Capacitor 8.5 — see Audit your custom code and plugins.

Update your plugin's .podspec file:

-  s.ios.deployment_target = '15.0'
+ s.ios.deployment_target = '16.0'

SPM compatible plugins

Update Package.swift file:

-    platforms: [.iOS(.v15)],
+ platforms: [.iOS(.v16)],

Plugins with old structure

Do the following for your Xcode project: select the Project within the project editor and open the Build Settings tab. Under the Deployment section, change iOS Deployment Target to iOS 16.0. Repeat the same steps for any app Targets.

Then, open ios/Podfile and update the iOS version to 16.0:

-platform :ios, '15.0'
+platform :ios, '16.0'

Update Capacitor SPM dependency

In SPM compatible plugins, update Package.swift file to point at the current Capacitor 9 prerelease (Capacitor 9 hasn't shipped a stable release yet, so this isn't a plain 9.0.0 version). Use the latest alpha, since earlier ones had crashes related to Cordova Optionality:

    dependencies: [
- .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "9.0.0-alpha.6")
],