# Kotlin Multiplatform - Docs

The PostHog Kotlin Multiplatform (KMP) SDK lets you capture analytics from shared Kotlin code that runs on Android, iOS, and the web. It's a thin wrapper that delegates to the official native PostHog SDKs on each target – [Android](/docs/libraries/android.md), [iOS](/docs/libraries/ios.md), and [JavaScript](/docs/libraries/js.md) – so you get native batching, queueing, and session replay behind a single common API.

> **Early access:** This SDK is currently in a `0.x` pre-release. The API may change between minor versions until it stabilizes. We'd love your feedback on [GitHub](https://github.com/PostHog/posthog-kmp/issues).

## Installation

Add the dependency to your shared module's `commonMain` source set:

shared/build.gradle.kts

PostHog AI

```kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.posthog:posthog-kmp:<version>")
        }
    }
}
```

You can find the latest version on [Maven Central](https://central.sonatype.com/artifact/com.posthog/posthog-kmp).

### Configure the SDK

Initialize PostHog once, early in your app's lifecycle, by calling `PostHog.setup()` with a `PostHogConfig` and a platform `PostHogContext`:

Kotlin

PostHog AI

```kotlin
import com.posthog.kmp.PostHog
import com.posthog.kmp.PostHogConfig
import com.posthog.kmp.PostHogContext
PostHog.setup(
    config = PostHogConfig(
        apiKey = "<ph_project_token>",
        host = "https://us.i.posthog.com",
    ),
    context = PostHogContext(), // platform-specific, see below
)
```

You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). For convenience, `PostHogConfig.HOST_US` and `PostHogConfig.HOST_EU` are provided as constants for the two PostHog Cloud hosts.

### Platform-specific setup

The only platform difference is how you build `PostHogContext`. Everything else – event capture, feature flags, config – is shared code.

#### Android

Android requires the `Application` instance, so pass it to `PostHogContext`:

Kotlin

PostHog AI

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        PostHog.setup(
            config = PostHogConfig(apiKey = "<ph_project_token>"),
            context = PostHogContext(application),
        )
    }
}
```

#### iOS

On iOS, use the no-argument `PostHogContext()`:

Kotlin

PostHog AI

```kotlin
// MainViewController.kt
fun MainViewController() = ComposeUIViewController {
    LaunchedEffect(Unit) {
        PostHog.setup(
            config = PostHogConfig(apiKey = "<ph_project_token>"),
            context = PostHogContext(),
        )
    }
    App()
}
```

#### Web

On the web, use the no-argument `PostHogContext()`:

Kotlin

PostHog AI

```kotlin
// main.kt
fun main() {
    PostHog.setup(
        config = PostHogConfig(apiKey = "<ph_project_token>"),
        context = PostHogContext(),
    )
}
```

## Capturing events

You can send custom events using `capture`:

Kotlin

PostHog AI

```kotlin
PostHog.capture("user_signed_up")
```

You can add properties to any event by passing a `properties` map:

Kotlin

PostHog AI

```kotlin
PostHog.capture(
    event = "purchase_completed",
    properties = mapOf(
        "product_id" to "SKU123",
        "price" to 29.99,
        "currency" to "USD",
    ),
)
```

To attach [group](/docs/product/group-analytics.md) context or a custom timestamp to a single event, pass `CaptureOptions`:

Kotlin

PostHog AI

```kotlin
import com.posthog.kmp.CaptureOptions
PostHog.capture(
    event = "feature_used",
    properties = mapOf("feature_name" to "export"),
    options = CaptureOptions(groups = mapOf("company" to "acme_corp")),
)
```

> **Note:** Properties with `null` values are dropped before the event is sent, so every platform receives the same event shape.

## Identifying users

> **Identifying users is required.** Call `posthog.identify('your-user-id')` after login to link events to a known user. This is what connects frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), and [error tracking](/docs/error-tracking.md) to the same person — and lets backend events link back too.
>
> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up.

Use `identify` to associate events with a specific user and, optionally, set [person properties](/docs/data/user-properties.md):

Kotlin

PostHog AI

```kotlin
PostHog.identify(
    distinctId = "user_123",
    userProperties = mapOf(
        "email" to "user@example.com",
        "plan" to "premium",
    ),
)
```

`userPropertiesSetOnce` works just like `userProperties`, except it only sets a property if the user doesn't already have it:

Kotlin

PostHog AI

```kotlin
PostHog.identify(
    distinctId = "user_123",
    userPropertiesSetOnce = mapOf(
        "first_seen" to "2025-01-01",
        "signup_source" to "organic",
    ),
)
```

## Anonymous and identified events

PostHog captures two types of events: [**anonymous** and **identified**](/docs/data/anonymous-vs-identified-events.md)

**Identified events** enable you to attribute events to specific users, and attach [person properties](/docs/product-analytics/person-properties.md). They're best suited for logged-in users.

Scenarios where you want to capture identified events are:

-   Tracking logged-in users in B2B and B2C SaaS apps
-   Doing user segmented product analysis
-   Growth and marketing teams wanting to analyze the *complete* conversion lifecycle

**Anonymous events** are events without individually identifiable data. They're best suited for [web analytics](/docs/web-analytics.md) or apps where users aren't logged in.

Scenarios where you want to capture anonymous events are:

-   Tracking a marketing website
-   Content-focused sites
-   B2C apps where users don't sign up or log in

Under the hood, the key difference between identified and anonymous events is that for identified events we create a [person profile](/docs/data/persons.md) for the user, whereas for anonymous events we do not.

> **Important:** Due to the reduced cost of processing them, anonymous events can be up to 4x cheaper than identified ones, so we recommended you only capture identified events when needed.

## Get the current user's distinct ID

You may find it helpful to get the current user's distinct ID – for example, to check whether you've already called `identify` for a user. Call `getDistinctId()`, which returns either the ID automatically generated by PostHog or the one passed to `identify()`:

Kotlin

PostHog AI

```kotlin
val distinctId = PostHog.getDistinctId()
```

## Alias

Sometimes you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible – for example, if a distinct ID used on the frontend isn't available in your backend. Use `alias` to assign another distinct ID to the same user:

Kotlin

PostHog AI

```kotlin
PostHog.alias("distinct_id")
```

We strongly recommend reading our docs on [alias](/docs/data/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method.

## Setting person properties

Besides setting properties during `identify`, you can set person properties directly with `setPersonProperties`:

Kotlin

PostHog AI

```kotlin
PostHog.setPersonProperties(
    userProperties = mapOf("plan" to "premium"),
    userPropertiesSetOnce = mapOf("first_seen" to "2025-01-01"),
)
```

## Super properties

Super properties are properties you set once and then send with every subsequent `capture` call. They're set with `PostHog.register`, which takes a key and value, and they persist across sessions:

Kotlin

PostHog AI

```kotlin
PostHog.register("team_id", 22)
```

The call above ensures every event sent by the user includes `"team_id": 22`.

> **Note:** This stores properties against the user's events, not against the user profile itself. To store properties against the person, use `identify` or `setPersonProperties`.

To stop sending a super property with events, use `unregister`:

Kotlin

PostHog AI

```kotlin
PostHog.unregister("team_id")
```

If you're doing this as part of a user logging out, you can instead call `PostHog.reset()`, which clears all stored super properties and more.

## Feature flags

PostHog's [feature flags](/docs/feature-flags.md) enable you to safely deploy and roll back new features as well as target specific users and groups with them.

By default, flags are preloaded on setup (`preloadFeatureFlags = true`). Check whether a flag is enabled:

Kotlin

PostHog AI

```kotlin
if (PostHog.isFeatureEnabled("new_dashboard")) {
    showNewDashboard()
}
```

Get the value of a [multivariate flag](/docs/feature-flags/creating-feature-flags.md#multivariate-feature-flags):

Kotlin

PostHog AI

```kotlin
when (PostHog.getFeatureFlag("pricing_experiment")) {
    "control" -> showOriginalPricing()
    "variant_a" -> showNewPricing()
    "variant_b" -> showPremiumPricing()
}
```

Get a detailed result, including the flag's [payload](/docs/feature-flags/payloads.md):

Kotlin

PostHog AI

```kotlin
val result = PostHog.getFeatureFlagResult("new_feature")
if (result != null) {
    println("Enabled: ${result.enabled}")
    println("Variant: ${result.variant}")
    println("Value: ${result.value}") // variant if present, otherwise enabled
    // Cast the payload to an expected type
    val payload = result.getPayloadAs<Map<String, Any>>()
}
```

Get every evaluated flag at once as a map of `FeatureFlagResult`:

Kotlin

PostHog AI

```kotlin
val allFlags = PostHog.getAllFeatureFlags()
```

Feature flags are fetched on setup and cached. If a user's properties change (for example, after `identify`), reload them so they reflect the latest state:

Kotlin

PostHog AI

```kotlin
PostHog.reloadFeatureFlags {
    // flags are now up to date
}
```

## Experiments (A/B tests)

Since [experiments](/docs/experiments/start-here.md) use feature flags, running one is very similar to the feature flag code above:

Kotlin

PostHog AI

```kotlin
if (PostHog.getFeatureFlag("experiment-feature-flag-key") == "variant-name") {
    // do something
}
```

It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md).

## Group analytics

Group analytics lets you associate a user's events with a group (for example, a company, team, or organization). Read the [group analytics](/docs/product/group-analytics.md) guide for more information.

> **Note:** This is a paid feature and is not available on the free plan. Learn more on the [pricing page](/pricing.md).

Associate the current user's events with a group:

Kotlin

PostHog AI

```kotlin
// "company" is the group type, "acme_corp" is the group key
PostHog.group(type = "company", key = "acme_corp")
```

Associate with a group and update that group's properties at the same time:

Kotlin

PostHog AI

```kotlin
PostHog.group(
    type = "company",
    key = "acme_corp",
    groupProperties = mapOf(
        "name" to "Acme Corporation",
        "plan" to "enterprise",
    ),
)
```

The `name` is a special property used in the PostHog UI for the group's name. If you don't specify it, the group key is used instead.

## Screen views

Capture a [screen view](/docs/product-analytics/capture-events.md#screen-views) with `screen`:

Kotlin

PostHog AI

```kotlin
PostHog.screen("Home")
```

You can add properties too:

Kotlin

PostHog AI

```kotlin
PostHog.screen(
    screenName = "Product Details",
    properties = mapOf(
        "product_id" to "SKU123",
        "category" to "Electronics",
    ),
)
```

To automatically capture screen views on Android, set `captureScreenViews = true` in your config.

## Error tracking

Capture handled exceptions with their stack traces so they show up in [error tracking](/docs/error-tracking.md):

Kotlin

PostHog AI

```kotlin
try {
    riskyOperation()
} catch (e: Exception) {
    PostHog.captureException(
        throwable = e,
        additionalProperties = mapOf("context" to "checkout_flow"),
    )
}
```

## Session replay

[Session replay](/docs/session-replay/mobile.md) is available on Android and iOS. To enable it, turn on "Record user sessions" in [your project settings](https://us.posthog.com/settings/project-replay) and pass a `SessionRecordingConfig`:

Kotlin

PostHog AI

```kotlin
import com.posthog.kmp.SessionRecordingConfig
PostHog.setup(
    config = PostHogConfig(
        apiKey = "<ph_project_token>",
        sessionRecording = SessionRecordingConfig(
            enabled = true,
            maskAllTextInputs = true, // mask sensitive text (default)
            maskAllImages = true,     // mask images (default)
        ),
    ),
    context = PostHogContext(),
)
```

You can also enable experimental screenshot mode instead of the default wireframe recording:

Kotlin

PostHog AI

```kotlin
sessionRecording = SessionRecordingConfig(
    enabled = true,
    screenshot = true,   // capture screenshots instead of wireframes
    maskAllImages = true, // recommended when using screenshots
)
```

> **Warning:** Screenshots may contain sensitive information. Make sure masking is configured appropriately before enabling screenshot mode.

## Opt out of data capture

You can opt users out of data capture in two ways.

Opt users out by default by setting `optOut` to `true` in your config:

Kotlin

PostHog AI

```kotlin
PostHog.setup(
    config = PostHogConfig(apiKey = "<ph_project_token>", optOut = true),
    context = PostHogContext(),
)
```

Or opt a user out (and back in) at runtime:

Kotlin

PostHog AI

```kotlin
PostHog.optOut()
PostHog.optIn()
```

Check whether a user is currently opted out:

Kotlin

PostHog AI

```kotlin
if (PostHog.isOptedOut()) {
    showConsentBanner()
}
```

## Session management

Access the identifiers PostHog uses for the current session:

Kotlin

PostHog AI

```kotlin
val anonymousId = PostHog.getAnonymousId() // before identification
val sessionId = PostHog.getSessionId()
val distinctId = PostHog.getDistinctId()
```

## Flush

You can configure how many events queue before flushing with `flushAt`. Setting this to `1` will send events immediately and will use more battery. The default is `20`.

You can also manually flush the queue to start sending events immediately instead of waiting for the next batch:

Kotlin

PostHog AI

```kotlin
PostHog.flush()
```

Flushing is best-effort and asynchronous – it starts sending queued events in the background but doesn't wait for the request to finish, so it isn't a delivery guarantee.

## Reset after logout

To reset the user's ID and anonymous ID, call `reset`. Usually you'd do this right after the user logs out:

Kotlin

PostHog AI

```kotlin
PostHog.reset()
```

## Close

To flush queued events and release resources during a clean shutdown, call `close`:

Kotlin

PostHog AI

```kotlin
PostHog.close()
```

## Debug mode

If you're not seeing the events, feature flags, or session replay behavior you expect, enable debug mode to see verbose logs about what the SDK is doing:

Kotlin

PostHog AI

```kotlin
PostHog.setup(
    config = PostHogConfig(apiKey = "<ph_project_token>", debug = true),
    context = PostHogContext(),
)
// or at runtime
PostHog.setDebug(true)
```

## All configuration options

Pass these to `PostHogConfig` when calling `setup`.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| apiKey | String | Required | Your PostHog project token. |
| host | String | HOST_US | PostHog instance URL. Use PostHogConfig.HOST_US (https://us.i.posthog.com) or PostHogConfig.HOST_EU (https://eu.i.posthog.com). |
| debug | Boolean | false | Enables verbose SDK logging. |
| captureApplicationLifecycleEvents | Boolean | true | Captures app open/close lifecycle events (mobile). |
| captureScreenViews | Boolean | false | Automatically captures $screen events (Android). |
| captureDeepLinks | Boolean | true | Captures deep link opens (Android). |
| sendFeatureFlagEvent | Boolean | true | Sends $feature_flag_called when a flag is evaluated. |
| preloadFeatureFlags | Boolean | true | Fetches feature flags automatically during setup. |
| flushAt | Int | 20 | Number of queued events that triggers a flush. |
| flushIntervalSeconds | Int | 30 | Maximum delay before queued events are flushed. |
| maxQueueSize | Int | 1000 | Maximum number of events kept in the queue. |
| maxBatchSize | Int | 50 | Maximum number of events sent in one batch. |
| optOut | Boolean | false | Starts with analytics opted out. |
| personProfiles | PersonProfiles | IDENTIFIED_ONLY | When person profiles are created: IDENTIFIED_ONLY, ALWAYS, or NEVER. |
| sessionRecording | SessionRecordingConfig? | null | Enables and configures session replay (Android and iOS). |
| autocapture | Boolean | false | Enables automatic event capture where the native platform supports it. |

### Session recording options

Pass a `SessionRecordingConfig` to `PostHogConfig.sessionRecording`. Defaults match the native Android and iOS SDKs.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| enabled | Boolean | true | Enables session recording. |
| maskAllTextInputs | Boolean | true | Masks all text input values. |
| maskAllImages | Boolean | true | Masks all images. |
| captureNetworkTelemetry | Boolean | true | Includes network requests in the recording. |
| captureLogs | Boolean | false | Captures console and system logs (iOS and web). |
| screenshot | Boolean | false | Uses screenshots instead of wireframes (experimental, Android and iOS). |
| captureLogcat | Boolean | true | Captures Android logcat output (Android only). |
| debouncerDelayMs | Long | 1000 | Touch event debounce delay in milliseconds (Android only). |

## Platform support

| Platform | Status | Backed by |
| --- | --- | --- |
| Android | ✅ | [PostHog Android](/docs/libraries/android.md) (native) |
| iOS | ✅ | [PostHog iOS](/docs/libraries/ios.md) (native, via a Swift bridge) |
| Web | ✅ | [PostHog.js](/docs/libraries/js.md) |

Because the SDK delegates to the native library on each platform, feature availability and behavior follow the underlying SDK. Session replay is available on Android and iOS; the web target inherits PostHog.js behavior.

## FAQ

### Which platforms does the KMP SDK support?

Android, iOS, and web. The SDK is designed for Kotlin Multiplatform projects – including Compose Multiplatform – that share business logic across these targets.

### Is this different from the Android SDK?

Yes. The [Android SDK](/docs/libraries/android.md) is for native Android apps. The KMP SDK is for shared Kotlin code targeting multiple platforms, and it wraps the native Android, iOS, and web SDKs so you can call one common API from `commonMain`.

### Why don't I see surveys or logs?

The KMP wrapper currently exposes event capture, identification, feature flags, group analytics, screen tracking, session replay, and error tracking. Surveys and logs aren't wrapped yet. Follow along or request them on [GitHub](https://github.com/PostHog/posthog-kmp/issues).

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better