Skip to content
Getting Started

KMP Quickstart

Ship a Kotlin Multiplatform app to both the App Store and Google Play from a single Shipfile.

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 .ipa and .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 inspect

You 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: internal

Ensure gradle.properties contains the version keys:

# gradle.properties
versionName=1.0.0
versionCode=1

3. Run doctor

shipit doctor

When 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 android

The 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 build

With 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 Store

Build system auto-detection

When build_system is omitted, ShipItSwifty detects it automatically:

Project markerDetected value
pubspec.yaml with flutter: keyflutter
package.json with react-native dependencyreact_native
build.gradle.kts applying kotlin("multiplatform")kmp
None of the abovenative

Common pitfalls

SymptomLikely causeFix
xcodebuild reports Shared.framework not foundFramework wasn't linked before xcodebuildEnsure ios.build_system: kmp is set
Wrong simulator architecture during linkTargeting Intel sim on Apple siliconSet ios.kmp_build_target: IosX64 for the matching target
versionName not updatedCustom key names in gradle.propertiesOverride via versioning.marketing_key / versioning.build_key
Gradle re-links every runExpected — Gradle's incremental check runs but skips when inputs unchangedNo action needed

Next steps