KMP Quickstart
Ship a Kotlin Multiplatform app to both the App Store and Google Play from a single Shipfile.
On this page
Ship a Kotlin Multiplatform Mobile (KMP) app to both the App Store and Google Play from a single Shipfile.
What you get
- One source tree → both
.ipaand.aab - The same App Store Connect / Google Play distribution pipeline as native projects
- Automatic build system detection from
build.gradle.kts - Shared version source via
gradle.properties(versionName+versionCode)
Prerequisites
- Xcode 15+ with command-line tools (
xcode-select --install) - JDK 17+ on PATH (
java -version) - A KMP project layout like:
./ gradlew settings.gradle.kts build.gradle.kts # applies kotlin("multiplatform") shared/ build.gradle.kts # the shared multiplatform module androidApp/ build.gradle.kts iosApp/ iosApp.xcodeproj or .xcworkspace - A Google Play service account JSON key (for Play Store uploads)
- App Store Connect API key (for TestFlight uploads)
1. Detect the project
From the project root, run:
shipit inspectYou should see:
detectedPlatform: ios
detectedBuildSystem: kmp
buildSystemFiles:
- build.gradle.kts
- settings.gradle.kts
If detectedBuildSystem is empty, ensure your root build.gradle.kts applies kotlin("multiplatform") or the org.jetbrains.kotlin.multiplatform plugin.
2. Write a Shipfile
# Shipfile.yml
app:
scheme: iosApp
workspace: iosApp/iosApp.xcworkspace
bundle_id: com.example.iosApp
team_id: ABCD123456
ios:
build_system: kmp
kmp_shared_module: shared
kmp_build_target: IosSimulatorArm64
kmp_archive_target: IosArm64
kmp_test_task: iosSimulatorArm64Test
android:
build_system: kmp
module: androidApp
gradle_project_dir: .
package_name: com.example.androidapp
versioning:
source: kmp
spec_path: gradle.properties
workflows:
beta-ios:
- action: test
options: { platform: ios }
- action: archive
options: { platform: ios }
- action: testflight
beta-android:
- action: test
options: { platform: android }
- action: archive
options: { platform: android }
- action: play-store
options:
track: internalEnsure gradle.properties contains the version keys:
# gradle.properties
versionName=1.0.0
versionCode=13. Run doctor
shipit doctorWhen a KMP build system is detected, doctor adds toolchain probes:
✓ Xcode installed
✓ xcodebuild available
✓ git on PATH
✓ java on PATH (for Gradle/Kotlin)
✓ gradlew present in project root
If java or ./gradlew is missing, fix that before proceeding — neither the iOS nor Android KMP path can run without them.
4. Build each platform
# iOS — runs `gradlew :shared:linkReleaseFrameworkIosSimulatorArm64` then xcodebuild
shipit build --platform ios
# Android — regular Gradle path against the android module
shipit build --platform androidThe iOS build path links the simulator framework first, then invokes xcodebuild against the wrapper workspace. Archives link the device target (IosArm64) before xcodebuild archive. The Android path is identical to a module-qualified native Gradle build.
5. Bump the shared version
shipit version bump --component buildWith versioning.source: kmp, this writes to gradle.properties instead of Info.plist. Both targets pick up the new value on the next build.
6. Run a full release workflow
shipit run beta-ios # test → archive (.ipa) → TestFlight
shipit run beta-android # test → archive (.aab) → Play StoreBuild system auto-detection
When build_system is omitted, ShipItSwifty detects it automatically:
| Project marker | Detected value |
|---|---|
pubspec.yaml with flutter: key | flutter |
package.json with react-native dependency | react_native |
build.gradle.kts applying kotlin("multiplatform") | kmp |
| None of the above | native |
Common pitfalls
| Symptom | Likely cause | Fix |
|---|---|---|
xcodebuild reports Shared.framework not found | Framework wasn't linked before xcodebuild | Ensure ios.build_system: kmp is set |
| Wrong simulator architecture during link | Targeting Intel sim on Apple silicon | Set ios.kmp_build_target: IosX64 for the matching target |
versionName not updated | Custom key names in gradle.properties | Override via versioning.marketing_key / versioning.build_key |
| Gradle re-links every run | Expected — Gradle's incremental check runs but skips when inputs unchanged | No action needed |
Next steps
- CI Setup — KMP-on-CI checklist with GitHub Actions examples
- Configuration Reference — full
build_systemreference - Android Quickstart — native Android setup