Skill 77 · Instrument Feature Flags
Subchapter 77.24
references/usage.mdMarkdown26 KBView on GitHub
AI agents: this is one page from PostHog’s docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt (opens in a new tab)
You can send custom events using capture:
Swift
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, orinvite sent.
Optionally, you can include additional information with the event by including a properties (opens in a new tab) object:
Swift
PostHogSDK.shared.capture("user_signed_up", properties: ["login_type": "email"], userProperties: ["is_free_trial": true])PostHog autocapture automatically tracks the following events for you:
UIViewController)UIKit based) and captureElementInteractions is enabledUIKit based)🚧 Note:
$autocaptureand$rageclickare captured from UIKit interactions. Some SwiftUI views use UIKit under the hood (for example,TextField→UITextFieldandToggle→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.
With configuration.captureScreenViews (opens in a new tab) 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.
Swift
PostHogSDK.shared.screen("Dashboard", properties: ["fromIcon": "bottom"])Important: While
captureScreenViewsworks with bothUIKitandSwiftUI, the screen names captured inSwiftUImay not be very meaningful as they are based on internal SwiftUI view identifiers. ForSwiftUIapplications, 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
BeforeSendBlockto filter or drop any undesired screen events, giving you control over which screen views are sent to PostHog. See Amending, dropping or sampling events (opens in a new tab) for implementation examples.
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.
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:
Swift
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:
Swift
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:
Swift
<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:
UITextField in this example)text value in this example)id attribute in this example)In the examples above, we can filter for the specific text field using the CSS Selector #usernameTextField
Interaction autocapture records when users interact with UI elements in your app. This includes:
touch, swipe, pan, pinch, rotation, long_press, scrollvalue_changed, submit, toggle, primary_action, menu_action, changeInteraction autocapture is not enabled by default. You can enable it by setting captureElementInteractions to true in the config.
Swift
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
config.captureElementInteractions = true // Disabled by default
PostHogSDK.shared.setup(config)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).
Swift
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)You can enable or disable autocapture through the PostHogConfig object. Find more details about autocapture configuration in the configuration page (opens in a new tab).
To exclude specific UI elements from autocapture or Session Replay, add ph-no-capture as either an accessibilityLabel or accessibilityIdentifier. See privacy controls (opens in a new tab) for masking behavior and iOS examples.
We highly recommend reading our section on Identifying users (opens in a new tab) 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:
distinct_id which uniquely identifies your user in your database
userProperties: Optional. A dictionary with key:value pairs to set the person properties (opens in a new tab)
userPropertiesSetOnce: Optional. Similar to userProperties. See the difference between userProperties and userPropertiesSetOnce (opens in a new tab)
Swift
PostHogSDK.shared.identify("user_id_from_your_database",
userProperties: ["name": "Peter Griffin", "email": "peter@familyguy.com"],
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.
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().
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.
Swift
PostHogSDK.shared.alias("alias_id")We strongly recommend reading our docs on alias (opens in a new tab) to best understand how to correctly use this method.
PostHog captures two types of events: anonymous and identified (opens in a new tab)
Identified events enable you to attribute events to specific users, and attach person properties (opens in a new tab). They’re best suited for logged-in users.
Scenarios where you want to capture identified events are:
Anonymous events are events without individually identifiable data. They’re best suited for web analytics (opens in a new tab) or apps where users aren’t logged in.
Scenarios where you want to capture anonymous events are:
Under the hood, the key difference between identified and anonymous events is that for identified events we create a person profile (opens in a new tab) 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.
The iOS SDK captures anonymous events by default. However, this may change depending on your personProfiles config (opens in a new tab) when initializing PostHog:
personProfiles: .identifiedOnly (recommended) (default) - Anonymous events are captured by default. PostHog only captures identified events for users where person profiles (opens in a new tab) have already been created.
personProfiles: .always - Capture identified events for all events.
personProfiles: .never - Capture anonymous events for all events.
For example:
iOS
let config = PostHogConfig(
projectToken: POSTHOG_PROJECT_TOKEN,
host: POSTHOG_HOST
)
config.personProfiles = .identifiedOnly
PostHogSDK.shared.setup(config)If you’ve set the personProfiles config (opens in a new tab) 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 (opens in a new tab) 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.
To set properties (opens in a new tab) 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.
Swift
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.
Swift
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.
Swift
PostHogSDK.shared.setPersonProperties(userPropertiesToSet: ["plan": "Pro++"])
PostHogSDK.shared.setPersonProperties(
userPropertiesToSet: ["plan": "Pro++"],
userPropertiesToSetOnce: ["first_seen_source": "ios"]
)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:
Swift
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.
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:
Swift
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.
To reset the user’s ID and anonymous ID after logout, call reset. See Identifying users (opens in a new tab) for the shared reset guidance and iOS example.
Group analytics allows you to associate the events for that person’s session with a group (e.g. teams, organizations, etc.). See Group Analytics (opens in a new tab) 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 (opens in a new tab).
You can completely opt users out from data capture by default or on a per-person basis. See Complete opt-out (opens in a new tab) for iOS examples.
PostHog’s feature flags (opens in a new tab) enable you to safely deploy and roll back new features as well as target specific users and groups with them.
Swift
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
}Swift
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
}If your payload is a JSON object, you can decode it into a Decodable type:
Swift
struct FlagPayload: Decodable {
let title: String
}
if let result = PostHogSDK.shared.getFeatureFlagResult("flag-key"),
let payload = result.payloadAs(FlagPayload.self) {
// Use payload.title
}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:
Swift
for flag in PostHogSDK.shared.getAllFeatureFlags() ?? [] {
print(flag.key, flag.enabled, flag.variant as Any, flag.payload as Any)
}Feature flag values are cached. If something has changed with your user and you’d like to refetch their flag values, call:
Swift
PostHogSDK.shared.reloadFeatureFlags()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 didReceiveFeatureFlags notification to wait for the feature flag request to finish:
Swift
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// register for `didReceiveFeatureFlags` notification before SDK initialization
NotificationCenter.default.addObserver(
self,
selector: #selector(receiveFeatureFlags),
name: PostHogSDK.didReceiveFeatureFlags,
object: nil
)
let POSTHOG_PROJECT_TOKEN = "<ph_project_token>"
// usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
let POSTHOG_HOST = "https://us.i.posthog.com"
let config = PostHogConfig(projectToken: POSTHOG_PROJECT_TOKEN, host: POSTHOG_HOST)
PostHogSDK.shared.setup(config)
return true
}
// The "receiveFeatureFlags" method will be called when the SDK receives the feature flags from the server.
@objc func receiveFeatureFlags() {
print("receiveFeatureFlags called")
}
}Alternatively, you can use the completion block of the reloadFeatureFlags(_:) method. This allows you to execute logic immediately after the flags are reloaded:
Swift
// Reload feature flags and check if a specific feature is enabled
PostHogSDK.shared.reloadFeatureFlags {
if PostHogSDK.shared.isFeatureEnabled("flag-key") {
// do something
}
}To track when someone sees or interacts with a feature, use captureFeatureView and captureFeatureInteraction.
Swift
PostHogSDK.shared.captureFeatureView(flag: "flag-key", flagVariant: "variant-key")
PostHogSDK.shared.captureFeatureInteraction(flag: "flag-key", flagVariant: "variant-key")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+):
Swift
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)setup() means events captured synchronously during initialization (like Application Installed) carry your distinct ID instead of the SDK-generated UUID.
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.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./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 (opens in a new tab) for the cross-SDK overview.
Since experiments (opens in a new tab) use feature flags, the code for running an experiment is very similar to the feature flags code. See adding experiment code (opens in a new tab) for iOS examples.
It’s also possible to run experiments without using feature flags (opens in a new tab).
Starting with iOS 14, Apple will further restrict apps that track users. Any references to Apple’s AdSupport framework, even in strings, will trip (opens in a new tab) 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.
Note: Session replay is currently only available on iOS. For future macOS support, please follow and upvote this GitHub issue (opens in a new tab).
To set up session replay (opens in a new tab) in your project, all you need to do is install the iOS SDK, enable “Record user sessions” in your project settings (opens in a new tab) and enable the sessionReplay option.
Surveys (opens in a new tab) launched with popover presentation (opens in a new tab) are automatically shown to users matching the display conditions (opens in a new tab) you set up.
To set up error tracking in your project, see the error tracking docs (opens in a new tab).
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.
Swift
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.
Swift
// Enable debug mode
PostHogSDK.shared.debug(true)
// Disable debug mode
PostHogSDK.shared.debug(false)Ask PostHog AI
HelpfulCould be better
Nearby