Generated banner for Instrument Product Analytics
Generated logo for Instrument Product Analytics

Instrument Product Analytics

PostHogOfficialCreated by PostHog

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

From the author

Add PostHog product analytics events to track user behavior. Use after implementing new features or reviewing PRs to ensure meaningful user actions are captured. Also handles initial PostHog SDK setup if not yet installed.
— 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 workflow to integrate PostHog product analytics into a wide variety of software projects. It guides the user through detecting the project's framework, installing the appropriate SDK, initializing the service, and strategically placing event capture calls. The skill emphasizes non-intrusive implementation, requiring that new analytics code be added alongside existing logic without altering the project's fundamental architecture. It also covers user identification, error tracking, and secure environment variable management.

What it does

  • Detects the project's framework and language to determine the correct PostHog SDK.
  • Handles initial SDK installation and initialization based on framework-specific patterns.
  • Identifies key user actions and business-critical events for tracking.
  • Implements event capture calls and user identification logic.
  • Configures environment variables securely for API tokens and host URLs.
  • Verifies the integration and performs targeted linting on modified files.

When to use it

  • After implementing new features that require tracking for product analytics.
  • When reviewing pull requests to ensure meaningful user behaviors are captured.
  • During the initial setup of a new project that needs product analytics.
  • When migrating or adding analytics to an existing application.

Advantages

  • Supports a broad range of frontend, backend, and mobile frameworks.
  • Ensures consistent event naming and property usage across the codebase.
  • Prioritizes non-breaking changes and minimal impact on existing code structure.
  • Automates the complex setup of environment variables and SDK initialization.

Usage examples

“Add PostHog analytics to my Next.js project.”
The agent will analyze the project structure, install the PostHog SDK, initialize it in the appropriate client/server files, and prompt you to identify key events to track.
“I need to track user signups and errors in my Django backend.”
The agent will locate your authentication logic, add identify calls, implement exception capture, and ensure the PostHog project tokens are managed via environment variables.

Install

  1. Download the skill as a ZIP:

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

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

Documentation by PostHog

Add PostHog product analytics events

Use this skill to add product analytics events (capture calls) that track meaningful user actions in new or changed code. Use it after implementing features or reviewing PRs to ensure key user behaviors are captured. If PostHog is not yet installed, this skill also covers initial SDK setup. Supports any framework or language.

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

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, skip to STEP 5.

STEP 2: Research integration. (Skip if PostHog is already set up.) 2.1. Find the reference file below that matches the detected framework — it is the source of truth for SDK initialization, provider setup, and event capture 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. (Skip if PostHog is already set up.)

  • 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. (Skip if PostHog is already set up.)

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

STEP 5: Plan event tracking.

  • From the project's file list, select between 10 and 15 files that might have interesting business value for event tracking, especially conversion and churn events.

  • Also look for files related to login that could be used for identifying users, along with error handling.

  • Find any existing posthog.capture() code. Make note of event name formatting. Don't duplicate existing events; supplement them.

  • Track actions only, not pageviews (those can be captured automatically). Exceptions can be made for "viewed"-type events at the top of a conversion funnel.

  • Server-side events are REQUIRED if the project includes any instrumentable server-side code (API routes, server actions, webhook handlers, payment/checkout completion, authentication endpoints).

STEP 6: Implement event capture.

  • For each planned event, add posthog.capture() calls with useful properties.

  • If a file already has existing integration code for other tools or services, don't overwrite or remove that code. Place PostHog code below it.

  • Do not alter the fundamental architecture of existing files. Make additions minimal and targeted.

  • You must read a file immediately before attempting to write it.

STEP 7: Identify users.

  • Add PostHog identify() calls on the client side during login and signup events. Use the contents of login and signup forms to identify users on submit.

  • 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. On the server side, make sure events have a matching distinct ID.

STEP 8: Add error tracking.

  • Add PostHog exception capture error tracking to relevant files, particularly around critical user flows and API boundaries.

STEP 9: 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 using the framework's naming convention.

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

STEP 10: 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-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-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-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/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 docs: Follow the framework reference's initialization and capture patterns exactly.

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-product-analytics · 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.

Download ZIP

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