This guide explains how to detect, interact with, and configure the Android target in a Kotlin Multiplatform (KMP) project from a custom Gradle plugin. Use these APIs when writing convention plugins for your build or publishing Gradle plugins for KMP projects.
Check if a project uses the Kotlin Multiplatform plugin
To avoid errors and to make sure that your plugin only runs when KMP is present,
you must check if the project uses the KMP plugin. It's best practice to use
plugins.withId() to react to the KMP plugin being applied, rather than
checking for it immediately. This reactive approach prevents your plugin from
being brittle to the order in which plugins are applied in the user's build
scripts.
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
// The KMP plugin is applied, you can now configure your KMP integration.
}
}
}
Access the model
The entry point for all Kotlin Multiplatform configurations is the
KotlinMultiplatformExtension extension.
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
}
}
}
React to Kotlin Multiplatform targets
Use the targets container to reactively configure your plugin for each target
the user adds.
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
// 'target' is an instance of KotlinTarget
val targetName = target.name // for example, "android", "iosX64", "jvm"
val platformType = target.platformType // for example, androidJvm, jvm, native, js
}
}
}
}
Apply target-specific logic
If your plugin needs to apply logic only to certain types of platforms, a common
approach is to check the platformType property. This is an enum that broadly
categorizes the target.
For example, use this if your plugin only needs to differentiate broadly (such as running only on JVM-like targets):
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
when (target.platformType) {
KotlinPlatformType.jvm -> { /* Standard JVM */ }
KotlinPlatformType.androidJvm -> { /* Android */ }
KotlinPlatformType.js -> { /* JavaScript */ }
KotlinPlatformType.native -> { /* Any Native (iOS, Linux, Windows, etc.) */ }
KotlinPlatformType.wasm -> { /* WebAssembly */ }
KotlinPlatformType.common -> { /* Metadata target (rarely needs direct plugin interaction) */ }
}
}
}
}
}
Android-specific details
While all Android targets have the platformType.androidJvm indicator, KMP has
two distinct integration points depending on the Android Gradle plugin used:
KotlinAndroidTarget for projects using com.android.library or
com.android.application, and KotlinMultiplatformAndroidLibraryTarget for
projects using com.android.kotlin.multiplatform.library.
The KotlinMultiplatformAndroidLibraryTarget API was added in AGP 8.8.0, so if
the consumers of your plugin are running on a lower version of AGP, checking
target is KotlinMultiplatformAndroidLibraryTarget might result in a
ClassNotFoundException. To make this safe, check
AndroidPluginVersion.getCurrent() before checking the target type.
AndroidPluginVersion.getCurrent() requires AGP 8.3.1 or higher.
import com.android.build.api.AndroidPluginVersion
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
if (target is KotlinAndroidTarget) {
// Old kmp android integration using com.android.library or com.android.application
} else if (
// Check platformType first, so AGP classes load only for Android targets
target.platformType == KotlinPlatformType.androidJvm &&
AndroidPluginVersion.getCurrent() >= AndroidPluginVersion(8, 8) &&
target is KotlinMultiplatformAndroidLibraryTarget
) {
// New kmp android integration using com.android.kotlin.multiplatform.library
}
}
}
}
}
Access the Android KMP extension and its properties
Your plugin primarily interacts with the Kotlin extension provided
by the Kotlin Multiplatform plugin and the Android extension provided by AGP
for the KMP Android target. The android {} block within the Kotlin extension
in a KMP project is represented by the KotlinMultiplatformAndroidLibraryTarget
interface, which also extends KotlinMultiplatformAndroidLibraryExtension.
This means you can access both target-specific and Android-specific DSL
properties through this single object.
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
// Access the Android target, which also serves as the Android-specific DSL extension
kmpExtension.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).configureEach { androidTarget ->
// You can now access properties and methods from both
// KotlinMultiplatformAndroidLibraryTarget and KotlinMultiplatformAndroidLibraryExtension
androidTarget.compileSdk = 34
androidTarget.namespace = "com.example.myplugin.library"
androidTarget.withJava() // enable Java sources
}
}
}
}
Unlike other Android plugins (such as com.android.library or
com.android.application), the KMP Android plugin doesn't register its main
DSL extension at the project level. It lives within the KMP target hierarchy
to make sure it only applies to the specific Android target defined in your
multiplatform setup.
Handle compilations and source sets
Often, plugins need to work at a more granular level than just the target,
specifically at the compilation level. The
KotlinMultiplatformAndroidLibraryTarget contains
KotlinMultiplatformAndroidCompilation instances (for example, main,
hostTest, deviceTest). Each compilation is associated with Kotlin source
sets. Plugins can interact with these to add sources, dependencies, or configure
compilation tasks.
import com.android.build.api.dsl.KotlinMultiplatformAndroidCompilation
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
target.compilations.configureEach { compilation ->
// standard compilations are usually 'main' and 'test'
// android target has 'main', 'hostTest', 'deviceTest'
val compilationName = compilation.name
// Access the default source set for this compilation
val defaultSourceSet = compilation.defaultSourceSet
// Access the Android-specific compilation DSL
if (compilation is KotlinMultiplatformAndroidCompilation) {
}
// Access and configure the Kotlin compilation task
compilation.compileTaskProvider.configure { compileTask ->
}
}
}
}
}
}
Configure test compilations in convention plugins
When configuring default values for test compilations (such as targetSdk for
instrumented tests) in a convention plugin, avoid using enabler methods like
withDeviceTest { } or withHostTest { }. Calling these methods eagerly
triggers the creation of the corresponding Android test variants and
compilations for every module that applies the convention plugin, which might
not be suitable. These methods also can't be called a second time in a specific
module to refine settings, because doing so throws an error stating the
compilation has already been created.
Instead, use a reactive configureEach block on the compilations container.
This lets you provide default configurations that only apply if and when a
module explicitly enables the test compilation:
import com.android.build.api.dsl.KotlinMultiplatformAndroidDeviceTestCompilation
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension =
project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java)
.configureEach { androidTarget ->
androidTarget.compilations.withType(
KotlinMultiplatformAndroidDeviceTestCompilation::class.java
).configureEach {
it.targetSdk { version = release(34) }
}
}
}
}
}
This pattern makes sure that your convention plugin remains lazy and allows
individual modules to call withDeviceTest { } to enable and further customize
their tests without conflicting with the defaults.
Interact with the Variant API
For tasks that require late-stage configuration, artifact access (like
manifests or byte-code), or the ability to enable or disable specific
components, you must use the Android Variant API. In KMP projects, the
extension is of type KotlinMultiplatformAndroidComponentsExtension.
The extension is registered at the project level when the KMP Android plugin is applied.
Use beforeVariants to control the creation of variants or their nested test
components (hostTests and deviceTests). This is the correct place to
programmatically disable tests or change DSL properties' values.
import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val androidComponents = project.extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)
androidComponents?.beforeVariants { variantBuilder ->
// Disable all tests for this module
variantBuilder.hostTests.values.forEach { it.enable = false }
variantBuilder.deviceTests.values.forEach { it.enable = false }
}
}
}
}
Use onVariants to access the final variant object
(KotlinMultiplatformAndroidVariant). This is where you can inspect resolved
properties or register transformations on artifacts like the merged manifest
or library classes.
import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val androidComponents = project.extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)
androidComponents?.onVariants { variant ->
// 'variant' is a KotlinMultiplatformAndroidVariant
val variantName = variant.name
// Access the artifacts API
val manifest = variant.artifacts.get(com.android.build.api.artifact.SingleArtifact.MERGED_MANIFEST)
}
}
}
}
Recommended for you
- Note: link text is displayed when JavaScript is off
- Set up your environment
- Add KMP module to a project
- Set up the Android Gradle Library Plugin for KMP