Kotlin Multiplatform

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, iOS, and JavaScript – 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.

Installation

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

shared/build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.posthog:posthog-kmp:<version>")
}
}
}

You can find the latest version on Maven Central.

Configure the SDK

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

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. 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
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
// 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
// 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.capture("user_signed_up")

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

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

To attach group context or a custom timestamp to a single event, pass CaptureOptions:

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, LLM traces, and error tracking to the same person — and lets backend events link back too.

See our guide on identifying users for how to set this up.

Use identify to associate events with a specific user and, optionally, set person properties:

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.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

Identified events enable you to attribute events to specific users, and attach person properties. 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 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 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
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.alias("distinct_id")

We strongly recommend reading our docs on alias 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.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.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.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 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
if (PostHog.isFeatureEnabled("new_dashboard")) {
showNewDashboard()
}

Get the value of a multivariate flag:

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

Get a detailed result, including the flag's payload:

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
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.reloadFeatureFlags {
// flags are now up to date
}

Experiments (A/B tests)

Since experiments use feature flags, running one is very similar to the feature flag code above:

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

It's also possible to run experiments without using feature flags.

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 guide for more information.

Note: This is a paid feature and is not available on the free plan. Learn more on the pricing page.

Associate the current user's events with a group:

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.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 with screen:

Kotlin
PostHog.screen("Home")

You can add properties too:

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:

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

Session replay

Session replay is available on Android and iOS. To enable it, turn on "Record user sessions" in your project settings and pass a SessionRecordingConfig:

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
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.setup(
config = PostHogConfig(apiKey = "<ph_project_token>", optOut = true),
context = PostHogContext(),
)

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

Kotlin
PostHog.optOut()
PostHog.optIn()

Check whether a user is currently opted out:

Kotlin
if (PostHog.isOptedOut()) {
showConsentBanner()
}

Session management

Access the identifiers PostHog uses for the current session:

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.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.reset()

Close

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

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.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.

OptionTypeDefaultDescription
apiKeyStringRequiredYour PostHog project token.
hostStringHOST_USPostHog instance URL. Use PostHogConfig.HOST_US (https://us.i.posthog.com) or PostHogConfig.HOST_EU (https://eu.i.posthog.com).
debugBooleanfalseEnables verbose SDK logging.
captureApplicationLifecycleEventsBooleantrueCaptures app open/close lifecycle events (mobile).
captureScreenViewsBooleanfalseAutomatically captures $screen events (Android).
captureDeepLinksBooleantrueCaptures deep link opens (Android).
sendFeatureFlagEventBooleantrueSends $feature_flag_called when a flag is evaluated.
preloadFeatureFlagsBooleantrueFetches feature flags automatically during setup.
flushAtInt20Number of queued events that triggers a flush.
flushIntervalSecondsInt30Maximum delay before queued events are flushed.
maxQueueSizeInt1000Maximum number of events kept in the queue.
maxBatchSizeInt50Maximum number of events sent in one batch.
optOutBooleanfalseStarts with analytics opted out.
personProfilesPersonProfilesIDENTIFIED_ONLYWhen person profiles are created: IDENTIFIED_ONLY, ALWAYS, or NEVER.
sessionRecordingSessionRecordingConfig?nullEnables and configures session replay (Android and iOS).
autocaptureBooleanfalseEnables 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.

OptionTypeDefaultDescription
enabledBooleantrueEnables session recording.
maskAllTextInputsBooleantrueMasks all text input values.
maskAllImagesBooleantrueMasks all images.
captureNetworkTelemetryBooleantrueIncludes network requests in the recording.
captureLogsBooleanfalseCaptures console and system logs (iOS and web).
screenshotBooleanfalseUses screenshots instead of wireframes (experimental, Android and iOS).
captureLogcatBooleantrueCaptures Android logcat output (Android only).
debouncerDelayMsLong1000Touch event debounce delay in milliseconds (Android only).

Platform support

PlatformStatusBacked by
AndroidPostHog Android (native)
iOSPostHog iOS (native, via a Swift bridge)
WebPostHog.js

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 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.

Community questions

Was this page useful?

Questions about this page? or post a community question.