| name | kmp-project-structure |
| description | Canonical Kotlin Multiplatform project layout — source set hierarchy (commonMain, androidMain, iosMain, jvmMain, jsMain, wasmJsMain), module graph, and Gradle composite build patterns. Use this when bootstrapping a new KMP repo or restructuring an existing one. |
KMP Project Structure
Instructions
A Kotlin Multiplatform project is one Gradle build containing one or more shared modules plus per-platform application modules. The shared module compiles the same Kotlin source for every target, diffing only where expect/actual or platform source sets intervene.
1. Canonical layout
my-kmp-app/
├── settings.gradle.kts
├── build.gradle.kts # root, plugins in pluginManagement only
├── gradle/
│ └── libs.versions.toml # version catalog
├── shared/ # KMP library module
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/kotlin/ # pure Kotlin, every target
│ ├── commonTest/kotlin/
│ ├── androidMain/kotlin/
│ ├── androidUnitTest/kotlin/
│ ├── iosMain/kotlin/ # hierarchical — covers all iosX targets
│ ├── iosTest/kotlin/
│ ├── jvmMain/kotlin/
│ └── jsMain/kotlin/
├── androidApp/ # Android application
│ ├── build.gradle.kts
│ └── src/main/…
├── iosApp/ # Xcode project that consumes shared.xcframework
│ └── iosApp.xcodeproj
└── desktopApp/ # optional Compose for Desktop
└── build.gradle.kts
2. settings.gradle.kts
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "my-kmp-app"
include(":shared", ":androidApp", ":desktopApp")
3. shared/build.gradle.kts — target declarations
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
androidTarget {
compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
jvm()
js(IR) { browser(); nodejs() }
listOf(iosArm64(), iosSimulatorArm64(), iosX64()).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.core)
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.turbine)
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
}
}
4. Hierarchical source sets
Kotlin 1.9.20+ auto-creates the default hierarchy so you can put shared-Apple code in appleMain, shared-native code in nativeMain, and all-iOS code in iosMain without manually wiring dependsOn. If you need a custom intermediate set (e.g. mobileMain for Android + iOS), opt in:
applyDefaultHierarchyTemplate {
common {
group("mobile") {
withAndroidTarget()
withIos()
}
}
}
5. Module graph
androidApp ─┐
iosApp ─────┼──► :shared ─► :domain ─► :core
desktopApp ─┘ ▲
└────── :data
- Apps depend only on
:shared (or :feature-* modules), never directly on :data or :core.
- Pure-Kotlin modules (
:domain, :core) are themselves KMP libraries without Android plugin — faster Gradle config time.
6. Root build.gradle.kts
Keep it empty except for optional aggregation:
plugins {
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.composeMultiplatform) apply false
}
Checklist