Generated banner for Instrument Integration
Generated logo for Instrument Integration

Instrument Integration

PostHogOfficialCreated by PostHog

  • Skill
  • Tool integration
  • Data and AI
  • MIT
  • Low risk

From the author

Add PostHog SDK integration to your application. Use when setting up PostHog for the first time or reviewing PRs that need PostHog initialization. Covers SDK installation, provider setup, and basic configuration for any framework.
— the description by PostHog

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured, multi-step procedure for integrating the PostHog SDK into a wide range of web, mobile, and backend frameworks. It handles the detection of the project environment, installation of the appropriate SDK, initialization, user identification, and secure configuration of environment variables. The skill emphasizes following framework-specific patterns and maintaining an analytics contract, ensuring that PostHog is implemented consistently without disrupting existing application logic.

What it does

  • Detects the project's framework, language, and package manager.
  • Installs the correct PostHog SDK for the identified platform.
  • Configures PostHog initialization and provider setup based on framework-specific references.
  • Implements user identification logic for login and signup events.
  • Manages environment variables for project tokens and host URLs securely.
  • Verifies the integration and performs minimal cleanup.

When to use it

  • When setting up PostHog in a project for the first time.
  • When reviewing pull requests that require PostHog initialization.
  • When adding analytics to a new application across supported frameworks.
  • When migrating or updating PostHog configurations in an existing codebase.

Advantages

  • Ensures consistent implementation across diverse technology stacks.
  • Prevents hardcoding of sensitive project tokens and host URLs.
  • Follows established framework-specific best practices and patterns.
  • Minimizes the risk of breaking application boot or build processes.
  • Automates the repetitive aspects of SDK setup and environment configuration.

Usage examples

“Set up PostHog in my Next.js project.”
The agent will detect the Next.js environment, install the PostHog SDK, configure the necessary provider, and set up environment variables in .env.local.
“Integrate PostHog into this Android app.”
The agent will identify the Android/Kotlin structure, initialize PostHog in the Application class, and guide the setup of local.properties for configuration.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/posthog/instrument-integration/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip instrument-integration.zip -d .agents/skills

Documentation by PostHog

Add PostHog SDK integration

Use this skill to add the PostHog SDK to an application. Use it when setting up PostHog for the first time, or reviewing PRs that need PostHog initialization. Covers SDK installation, provider setup, and basic configuration. Supports any framework or language.

Supported frameworks and languages: Next.js, React, React Router, Vue, Nuxt, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, PHP, Ruby on Rails, Go, Elixir, Android, iOS, Swift, Flutter, React Native, Expo, Node.js, and vanilla JavaScript.

Instructions

Follow these steps IN ORDER:

STEP 1: Analyze the codebase and detect the platform.

Look for dependency files (package.json, pubspec.yaml, Podfile, Package.swift, requirements.txt, Gemfile, composer.json, go.mod, mix.exs, etc.) to determine the framework and language.

Look for lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb, go.sum, pubspec.lock, Podfile.lock, Package.resolved, mix.lock) to determine the package manager.

  • Check for existing PostHog setup. If PostHog is already installed and initialized, do not modify its code. Inform the user and skip to verification.

STEP 2: Research integration. 2.1. Find the reference file below that matches the detected framework — it is the source of truth for SDK initialization, provider setup, and configuration patterns. Read it now. 2.2. If no reference matches, fall back to your general knowledge and web search. Use posthog.com/docs as the primary search source.

STEP 3: Install the PostHog SDK.

  • Add the PostHog SDK package for the detected platform. Do not manually edit package.json — use the package manager's install command.

STEP 4: Initialize PostHog.

  • Follow the framework reference for where and how to initialize. This varies significantly by framework (e.g., instrumentation-client.ts for Next.js 15.3+, AppConfig.ready() for Django, create_app() for Flask).

  • Set up the PostHog provider/wrapper component if the framework requires one.

STEP 5: Identify users.

  • Add PostHog identify() calls on the client side during login and signup events.

  • If both frontend and backend exist, pass the client-side session and distinct ID using X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID headers to the server-side code.

STEP 6: Set up environment variables.

  • Check if the project already has PostHog environment variables configured (e.g. in .env, .env.local, or framework-specific env files). If valid values already exist, skip this step.

  • If the PostHog project token is missing, use the PostHog MCP server's projects-get tool to retrieve the project's api_token. If multiple projects are returned, ask the user which project to use. If the MCP server is not connected or not authenticated, ask the user for their PostHog project token instead.

  • For the PostHog host URL: check the projects-get MCP response for a region field — US maps to https://us.i.posthog.com, EU maps to https://eu.i.posthog.com. If the region is not available from the MCP response or from existing project configuration, ask the user: "Are you on PostHog US Cloud or EU Cloud?" Do not assume US Cloud.

  • Write these values to the appropriate env file (e.g. .env.local for Next.js, .env for others) using the framework's naming convention.

  • Reference these environment variables in code instead of hardcoding them.

STEP 7: Verify and clean up.

  • Check the project for errors. Look for type checking or build scripts in package.json.

  • Ensure any components created were actually used.

  • Run any linter or prettier-like scripts found in the package.json, but ONLY on the files you have edited or created during this session. Never run formatting or linting across the entire project's codebase.

Reference files

  • references/EXAMPLE-next-app-router.md - next-app-router example project code

  • references/EXAMPLE-next-pages-router.md - next-pages-router example project code

  • references/EXAMPLE-react-react-router-6.md - react-react-router-6 example project code

  • references/EXAMPLE-react-react-router-7-framework.md - react-react-router-7-framework example project code

  • references/EXAMPLE-react-react-router-7-data.md - react-react-router-7-data example project code

  • references/EXAMPLE-react-react-router-7-declarative.md - react-react-router-7-declarative example project code

  • references/EXAMPLE-react-vite.md - react-vite example project code

  • references/EXAMPLE-nuxt-3-6.md - nuxt-3-6 example project code

  • references/EXAMPLE-nuxt-4.md - nuxt-4 example project code

  • references/EXAMPLE-vue-3.md - vue-3 example project code

  • references/EXAMPLE-react-tanstack-router-file-based.md - react-tanstack-router-file-based example project code

  • references/EXAMPLE-react-tanstack-router-code-based.md - react-tanstack-router-code-based example project code

  • references/EXAMPLE-tanstack-start.md - tanstack-start example project code

  • references/EXAMPLE-sveltekit.md - sveltekit example project code

  • references/EXAMPLE-astro-static.md - astro-static example project code

  • references/EXAMPLE-astro-view-transitions.md - astro-view-transitions example project code

  • references/EXAMPLE-astro-ssr.md - astro-ssr example project code

  • references/EXAMPLE-astro-hybrid.md - astro-hybrid example project code

  • references/EXAMPLE-angular.md - angular example project code

  • references/EXAMPLE-javascript-node.md - javascript-node example project code

  • references/EXAMPLE-javascript-web.md - javascript-web example project code

  • references/EXAMPLE-django.md - django example project code

  • references/EXAMPLE-flask.md - flask example project code

  • references/EXAMPLE-fastapi.md - fastapi example project code

  • references/EXAMPLE-python.md - python example project code

  • references/EXAMPLE-laravel.md - laravel example project code

  • references/EXAMPLE-php.md - php example project code

  • references/EXAMPLE-ruby-on-rails.md - ruby-on-rails example project code

  • references/EXAMPLE-ruby.md - ruby example project code

  • references/EXAMPLE-android.md - android example project code

  • references/EXAMPLE-swift.md - swift example project code

  • references/EXAMPLE-react-native.md - react-native example project code

  • references/EXAMPLE-expo.md - expo example project code

  • references/next-js.md - Next.js

  • references/react.md - React

  • references/react-router-v6.md - React router v6

  • references/react-router-v7-framework-mode.md - React router v7 framework mode (remix v3)

  • references/react-router-v7-data-mode.md - React router v7 data mode

  • references/react-router-v7-declarative-mode.md - React router v7 declarative mode

  • references/nuxt-js-3-6.md - Nuxt.js (v3.0 to v3.6)

  • references/nuxt-js.md - Nuxt.js

  • references/vue-js.md - Vue.js

  • references/tanstack-start.md - Tanstack start

  • references/svelte.md - Svelte

  • references/astro.md - Astro

  • references/angular.md - Angular

  • references/js.md - JavaScript web

  • references/posthog-js.md - PostHog JavaScript web SDK

  • references/node.md - Node.js

  • references/posthog-node.md - PostHog Node.js SDK

  • references/django.md - Django

  • references/flask.md - Flask

  • references/python.md - Python

  • references/posthog-python.md - PostHog python SDK

  • references/dotnet.md - .net

  • references/elixir.md - Elixir

  • references/go.md - Go

  • references/laravel.md - Laravel

  • references/php.md - Php

  • references/ruby-on-rails.md - Ruby on rails

  • references/ruby.md - Ruby

  • references/android.md - Android

  • references/ios.md - Ios

  • references/usage.md - Ios SDK usage

  • references/configuration.md - Ios SDK configuration

  • references/flutter.md - Flutter

  • references/react-native.md - React native

  • references/identify-users.md - Identify users

  • references/COMMANDMENTS.md - Framework-specific rules the integration must follow

Each framework reference contains SDK-specific installation, initialization, and usage patterns. Find the one matching the user's stack.

Key principles

  • Environment variables: Always use environment variables for PostHog keys. Never hardcode them.

  • Minimal changes: Add PostHog code alongside existing integrations. Don't replace or restructure existing code.

  • Match the example: Your implementation should follow the example project's patterns as closely as possible.

  • Analytics contract: Treat event names, property names, and feature flag keys as part of an analytics contract. Reuse existing names and patterns found in the project. When introducing new ones, make them clear, descriptive, and consistent with existing conventions.

Files

references/usage.md

AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt

iOS SDK usage

Canonical URL: https://posthog.com/docs/libraries/ios/usage

Cloud region: This page uses US Cloud hosts. For an EU Cloud project, use https://eu.i.posthog.com instead of https://us.i.posthog.com (ingestion host), and use https://eu.posthog.com instead of https://us.posthog.com (app host for links).

On this page

  • Capturing events

  • Autocapture

  • Preventing sensitive data capture

  • Identifying users

  • Get the current user's distinct ID

  • Alias

  • Anonymous vs identified events

  • Setting person properties

  • Super properties

  • Reset after logout

  • Group analytics

  • Opt out of data capture

  • Feature flags

  • Experiments (A/B tests)

  • A note about IDFA (identifier for advertisers) collection in iOS 14

  • Session replay

  • Surveys

  • Error tracking

  • Debug mode

Capturing events

You can send custom events using capture:

PostHogSDK.shared.capture("user_signed_up")

Tip: We recommend using a [object] [verb] format for your event names, where [object] is the entity that the behavior relates to, and [verb] is the behavior itself. For example, project created, user signed up, or invite sent.

Setting event properties

Optionally, you can include additional information with the event by including a properties object:

PostHogSDK.shared.capture("user_signed_up", properties: ["login_type": "email"], userProperties: ["is_free_trial": true])

Autocapture

PostHog autocapture automatically tracks the following events for you:

  • Application Opened – when the app is opened from a closed state or when the app comes to the foreground (e.g. from the app switcher)

  • Application Backgrounded – when the app is sent to the background by the user

  • Application Installed – when the app is installed

  • Application Updated – when the app is updated

  • $screen – when the user navigates (if using UIViewController)

  • $autocapture – when the user interacts with elements in a screen (UIKit based) and captureElementInteractions is enabled

  • $rageclick – when the user rapidly taps in the same area (iOS/macCatalyst, UIKit based)

🚧 Note: $autocapture and $rageclick are captured from UIKit interactions. Some SwiftUI views use UIKit under the hood (for example, TextField → UITextField and Toggle → UISwitch), so those interactions may also be autocaptured. In other SwiftUI cases, interactions might still be captured, but element metadata (such as $elements_chain) may be incomplete.

Capturing screen views

With configuration.captureScreenViews set as true, PostHog will try to record all screen changes automatically.

If you want to manually send a new screen capture event, use the screen function.

PostHogSDK.shared.screen("Dashboard", properties: ["fromIcon": "bottom"])

Important: While captureScreenViews works with both UIKit and SwiftUI, the screen names captured in SwiftUI may not be very meaningful as they are based on internal SwiftUI view identifiers. For SwiftUI applications, we recommend turning this option off and instead using the .postHogScreenView() view modifier (see next section) to capture screen views with meaningful names.

Note: You can use the BeforeSendBlock to filter or drop any undesired screen events, giving you control over which screen views are sent to PostHog. See Amending, dropping or sampling events for implementation examples.

Capturing screen views in SwiftUI

To track a screen view in SwiftUI, apply the postHogScreenView modifier to your full-screen views. PostHog will send a $screen event when the onAppear action is executed and will infer a screen name based on the view's type. You can provide a custom name and event properties if needed.

HomeView.swift

// This will trigger a screen view event with $screen_name: "HomeViewContent"
struct HomeView: View {
    var body: some View {
        HomeViewContent()
            .postHogScreenView()
    }
}

// This will trigger a screen view event with $screen_name: "My Home View" and an additional event property from_button: "start"
struct HomeView: View {
    var body: some View {
        HomeViewContent()
            .postHogScreenView("My Home View", ["from_button": "start"])
    }
}

In SwiftUI, views can range from entire screens to small UI components. Unlike UIKit, SwiftUI doesn't clearly distinguish between these levels, which makes automatic tracking of full-screen views harder.

Adding a custom label on autocaptured elements

PostHog automatically captures interactions with various UI elements in your app, but these interactions are often identified by element type names (e.g., UIButton, UITextField, UILabel).

While this provides basic tracking, it can be challenging to pinpoint specific interactions with particular elements in your analytics. To make your data more meaningful and actionable, you can assign custom labels to any autocaptured element. These labels act as descriptive identifiers, making it easier to identify, filter, and analyze events in your reports.

Adding a custom label in UIKit

To assign a custom label to a UIView, use the postHogLabel property:

let view = UIView()
view.postHogLabel = "usernameTextField"

In this example, interactions with the UITextField will be captured with an additional identifier "usernameTextField".

Adding a custom label in SwiftUI

In SwiftUI, use the .postHogLabel(_:) modifier instead:

var body: some View {
    ...
    TextField("username", text: $username)
        .postHogLabel("usernameTextField")
}

Since SwiftUI's TextField uses UITextField under the hood, interactions with it will be autocaptured with the additional identifier "usernameTextField".

Example of generated analytics data

The generated analytics element in the examples above will have the following form:

<UITextField id="usernameTextField">text value</UITextField>

Filtering for labeled autocaptured elements in reports

To locate and filter interactions with specific elements in PostHog reports, you can use Autocapture element filters, such as:

  • Tag Name (UITextField in this example)

  • Text (text value in this example)

  • CSS Selector (the generated id attribute in this example)

In the examples above, we can filter for the specific text field using the CSS Selector #usernameTextField

Interaction autocapture

Interaction autocapture records when users interact with UI elements in your app. This includes:

  • User interactions like touch, swipe, pan, pinch, rotation, long_press, scroll

  • Control types value_changed, submit, toggle, primary_action, menu_action, change

Interaction autocapture is not enabled by default. You can enable it by setting captureElementInteractions to true in the config.

let config = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
config.captureElementInteractions = true // Disabled by default
PostHogSDK.shared.setup(config)
Rage click autocapture

Note: Rage click autocapture for iOS/macCatalyst is available in version 3.51.0+.

A rage click is when a user taps an area multiple times in quick succession (e.g more than 3 taps in 1 second).

This is captured as a $rageclick event. You can use this event to identify opportunities to improve your UI, since it's a good indication that users may be frustrated with your product.

It is enabled by default (rageClickConfig.enabled = true).

let config = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
config.rageClickConfig.enabled = true // Enabled by default
config.rageClickConfig.minimumTapCount = 3 // Optional, default is 3
config.rageClickConfig.thresholdPoints = 30 // Optional, default is 30
config.rageClickConfig.timeoutInterval = 1.0 // Optional, default is 1.0s
PostHogSDK.shared.setup(config)
Autocapture configuration

You can enable or disable autocapture through the PostHogConfig object. Find more details about autocapture configuration in the configuration page.

Preventing sensitive data capture

To exclude specific UI elements from autocapture or Session Replay, add ph-no-capture as either an accessibilityLabel or accessibilityIdentifier. See privacy controls for masking behavior and iOS examples.

Identifying users

We highly recommend reading our section on Identifying users to better understand how to correctly use this method.

Using identify, you can associate events with specific users. This enables you to gain full insights as to how they're using your product across different sessions, devices, and platforms.

An identify call has the following arguments:

PostHogSDK.shared.identify("user_id_from_your_database",
                            userProperties: ["name": "Peter Griffin", "email": "[email protected]"],
                            userPropertiesSetOnce: ["date_of_first_log_in": "2024-03-01"])

You should call identify as soon as you're able to. Typically, this is after your user logs in. This ensures that events sent during your user's sessions are correctly associated with them.

When you call identify, all previously tracked anonymous events will be linked to the user.

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

To do this, call getDistinctId(). This returns either the ID automatically generated by PostHog or the ID that has been passed by a call to identify().

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 is not available in your backend.

In this case, you can use alias to assign another distinct ID to the same user.

PostHogSDK.shared.alias("alias_id")

We strongly recommend reading our docs on alias to best understand how to correctly use this method.

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

How to capture anonymous events

The iOS SDK captures anonymous events by default. However, this may change depending on your personProfiles config when initializing PostHog:

  1. personProfiles: .identifiedOnly (recommended) (default) - Anonymous events are captured by default. PostHog only captures identified events for users where person profiles have already been created.

  2. personProfiles: .always - Capture identified events for all events.

  3. personProfiles: .never - Capture anonymous events for all events.

For example:

let config = PostHogConfig(
    projectToken: POSTHOG_PROJECT_TOKEN,
    host: POSTHOG_HOST
)
config.personProfiles = .identifiedOnly
PostHogSDK.shared.setup(config)
How to capture identified events

If you've set the personProfiles config to .identifiedOnly (the default option), anonymous events are captured by default. Then, to capture identified events, call any of the following functions:

When you call any of these functions, it creates a person profile for the user. Once this profile is created, all subsequent events for this user will be captured as identified events.

Alternatively, you can set personProfiles to .always to capture identified events by default.

Setting person properties

To set properties on your users via an event, you can leverage the event properties userProperties and userPropertiesSetOnce.

When capturing an event, you can pass a property called $set as an event property, and specify its value to be an object with properties to be set on the user that will be associated with the user who triggered the event.

PostHogSDK.shared.capture("signed_up", properties: ["plan": "Pro++"], userProperties: ["user_property_name": "your_value"])

userPropertiesSetOnce works just like userProperties, except that it will only set the property if the user doesn't already have that property set.

PostHogSDK.shared.capture("signed_up", properties: ["plan": "Pro++"], userPropertiesSetOnce: ["user_property_name": "your_value"])

Use setPersonProperties when you want to update the current person's profile without also capturing a custom event. This sends a $set event to PostHog.

PostHogSDK.shared.setPersonProperties(userPropertiesToSet: ["plan": "Pro++"])

PostHogSDK.shared.setPersonProperties(
    userPropertiesToSet: ["plan": "Pro++"],
    userPropertiesToSetOnce: ["first_seen_source": "ios"]
)

Super properties

Super properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, or anything else.

They are set using PostHogSDK.shared.register, which takes a properties object as a parameter, and they persist across sessions.

For example, take a look at the following call:

PostHogSDK.shared.register(["team_id": 22])

The call above ensures that every event sent by the user will include "team_id": 22. This way, if you filtered events by property using team_id = 22, it would display all events captured on that user after the PostHogSDK.shared.register call, since they all include the specified Super Property.

However, please note that this does not store properties against the User, only against their events. To store properties against the User object, you should use PostHogSDK.shared.identify. More information on this can be found on the Sending User Information section.

Removing stored super properties

Super properties persist across sessions so you have to explicitly remove them if they are no longer relevant. To stop sending a super property with events, you can use PostHogSDK.shared.unregister, like so:

PostHogSDK.shared.unregister("team_id")

This removes the super property and subsequent events will not include it.

If you are doing this as part of a user logging out, you can instead simply use PostHogSDK.shared.reset which clears all super properties and more.

Reset after logout

To reset the user's ID and anonymous ID after logout, call reset. See Identifying users for the shared reset guidance and iOS example.

Group analytics

Group analytics allows you to associate the events for that person's session with a group (e.g. teams, organizations, etc.). See Group Analytics for iOS examples and implementation details.

Note: This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the pricing page.

Opt out of data capture

You can completely opt users out from data capture by default or on a per-person basis. See Complete opt-out for iOS examples.

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.

Boolean feature flags
if let result = PostHogSDK.shared.getFeatureFlagResult("flag-key"), result.enabled {
    // Do something differently for this user

    // Optional: fetch the payload from the same evaluation result
    let matchedFlagPayload = result.payload
}
Multivariate feature flags
if let result = PostHogSDK.shared.getFeatureFlagResult("flag-key"), result.variant == "variant-key" { // replace "variant-key" with the key of your variant
    // Do something differently for this user

    // Optional: fetch the payload from the same evaluation result
    let matchedFlagPayload = result.payload
}
Typed payloads

If your payload is a JSON object, you can decode it into a Decodable type:

struct FlagPayload: Decodable {
    let title: String
}

if let result = PostHogSDK.shared.getFeatureFlagResult("flag-key"),
   let payload = result.payloadAs(FlagPayload.self) {
    // Use payload.title
}
Inspecting all feature flags

You can inspect all currently loaded feature flags with getAllFeatureFlags(). It returns each flag's key, enabled state, variant, and payload, and does not send a $feature_flag_called event, so calling it won't affect your experiment results or flag usage analytics:

for flag in PostHogSDK.shared.getAllFeatureFlags() ?? [] {
    print(flag.key, flag.enabled, flag.variant as Any, flag.payload as Any)
}
Reloading feature flags

Feature flag values are cached. If something has changed with your user and you'd like to refetch their flag values, call:

PostHogSDK.shared.reloadFeatureFlags()
Ensuring flags are loaded before usage

Every time a user opens the app, we send a request in the background to fetch the feature flags that apply to that user. We store those flags in the storage.

This means that for most screens, the feature flags are available immediately – except for the first time a user visits.

To handle this, you can use the onFeatureFlags callback to be notified when flags are loaded or change. It's available in version 3.89.0+:

let subscription = PostHogSDK.shared.onFeatureFlags { loaded in
    if loaded.errorsLoading {
        // Loading failed, so the flags are the last known values, which can be empty.
        // Fall back to your default experience if you need fresh values.
        return
    }
    if PostHogSDK.shared.isFeatureEnabled("flag-key") {
        // do something
    }
}

// Later, to stop receiving updates
subscription.unsubscribe()

The callback runs on the main thread. It's called when flags load – including from bootstrap values – and again each time they're reloaded, for example after reloadFeatureFlags(), reset(), or an identify() or group() call that changes the distinct ID or group. A reload doesn't have to change any values, so treat each call as "flags may have changed". If flags have already loaded when you register, it's called shortly afterwards with the current values. You can register before calling setup().

Callback parameters

The callback receives a PostHogFeatureFlagsLoaded object with the following properties:

  • flags: [String]: The keys of the enabled feature flags.

  • variants: [String: Any]: The values of the enabled feature flags, keyed by flag key. A value is true for an enabled boolean flag, or the variant key for a multivariate flag.

  • errorsLoading: Bool: true if the latest attempt to load feature flags failed, for example because the device is offline. The flags and variants then hold the last known values, which can be empty – for example, on a fresh install with no bootstrap values. In that case, isFeatureEnabled returns false because the flag is unknown, not because PostHog evaluated it as disabled.

On versions earlier than 3.89.0, observe the PostHogSDK.didReceiveFeatureFlags notification instead. Register the observer before calling setup():

let observer = NotificationCenter.default.addObserver(
    forName: PostHogSDK.didReceiveFeatureFlags,
    object: nil,
    queue: .main
) { _ in
    if PostHogSDK.shared.isFeatureEnabled("flag-key") {
        // do something
    }
}

// Later, to stop receiving updates
NotificationCenter.default.removeObserver(observer)

Alternatively, you can use the completion block of the reloadFeatureFlags(_:) method. This allows you to execute logic immediately after the flags are reloaded:

// Reload feature flags and check if a specific feature is enabled
PostHogSDK.shared.reloadFeatureFlags {
    if PostHogSDK.shared.isFeatureEnabled("flag-key") {
        // do something
    }
}
Tracking feature usage

To track when someone sees or interacts with a feature, use captureFeatureView and captureFeatureInteraction.

PostHogSDK.shared.captureFeatureView(flag: "flag-key", flagVariant: "variant-key")
PostHogSDK.shared.captureFeatureInteraction(flag: "flag-key", flagVariant: "variant-key")
Bootstrapping flags

Since there is a delay between initializing PostHog and fetching feature flags, feature flags are not always available immediately. This makes them unusable if you want to do something like redirecting a user to a different page based on a feature flag.

To have your feature flags available immediately, you can initialize PostHog with precomputed values until it has had a chance to fetch them. This is called bootstrapping. After the SDK fetches feature flags from PostHog, it will use those flag values instead of bootstrapped ones.

Set config.bootstrap before calling setup() to seed identity and flag values before the first /flags response (requires iOS SDK 3.66.0+):

let config = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
config.bootstrap = PostHogBootstrapConfig(
    distinctId: "distinct_id_of_your_user",
    isIdentifiedId: true,
    featureFlags: [
        "flag-1": true,
        "variant-flag": "control"
    ],
    featureFlagPayloads: nil
)
PostHogSDK.shared.setup(config)
  • Bootstrapped identity applies during setup. On a fresh install, setting it before setup() means events captured synchronously during initialization (like Application Installed) carry your distinct ID instead of the SDK-generated UUID.

    • An anonymous bootstrap (isIdentifiedId: false, the default) seeds the anonymous ID only when none is persisted yet. Once an anonymous ID exists on disk, or the person has been identified, the SDK ignores it.

    • An identified bootstrap (isIdentifiedId: true) is for a signed-in identity available to your app (for example, from a backend session token). On a fresh install, it seeds the distinct ID, marks the person identified, and generates a separate device ID. On a returning install, a matching anonymous ID is marked identified without emitting $identify; a different anonymous ID is merged via identify() when person profiles are enabled. This emits $identify unless capturing is opted out. A different, already-identified person is left untouched.

  • Bootstrapped flags are served until the first /flags response, then replaced. A complete /flags response takes over entirely, so bootstrapped-only keys don't persist past it. Only enabled flags are seeded: a true boolean or a non-empty variant string. A false or empty value is dropped, matching posthog-js. Seed payloads with the separate featureFlagPayloads option. Flag values and payloads must be JSON-serializable, or they're dropped. Bootstrapped flags are cleared on reset().

The feature-flags-loaded signal fires as soon as bootstrapped flags are applied, so startup logic can read them immediately. These SDKs don't support the sessionID bootstrap option. When person profiles are set to never, the SDK preserves a different anonymous identity instead of merging it into an identified bootstrap.

See the SDK bootstrapping guide for the cross-SDK overview.

Experiments (A/B tests)

Since experiments use feature flags, the code for running an experiment is very similar to the feature flags code. See adding experiment code for iOS examples.

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

A note about IDFA (identifier for advertisers) collection in iOS 14

Starting with iOS 14, Apple will further restrict apps that track users. Any references to Apple's AdSupport framework, even in strings, will trip the App Store's static analysis.

Hence starting with posthog-ios version 1.2.0 we have removed all references to Apple's AdSupport framework.

Session replay

Note: Session replay is currently only available on iOS. For future macOS support, please follow and upvote this GitHub issue.

To set up session replay in your project, all you need to do is install the iOS SDK, enable "Record user sessions" in your project settings and enable the sessionReplay option.

Surveys

Surveys launched with popover presentation are automatically shown to users matching the display conditions you set up.

Error tracking

To set up error tracking in your project, see the error tracking docs.

Debug mode

If you're not seeing the expected events being captured, the feature flags being evaluated, or the surveys being shown, you can enable debug mode to see what's happening.

You can enable debug mode by setting the debug option to true in the PostHogConfig object. A common pattern is to set this to true in development environments only for local development.

let config = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
config.debug = true
PostHogSDK.shared.setup(config)

This will enable verbose logs about the inner workings of the SDK.

You can also toggle debug by calling the PostHogSDK.shared.debug() method in your code.

// Enable debug mode
PostHogSDK.shared.debug(true)

// Disable debug mode
PostHogSDK.shared.debug(false)

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python, Needs Node.js

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
PostHog
Publisher
PostHog (Official)
Source
PostHog/skills · skills/omnibus/instrument-integration · commit a5d30e7
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by PostHog or PostHog.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download instrument-integration.zip