Implement Feature Flags (Swift)
Overview
This developer guide will assist you in configuring your iOS/macOS platform for Feature Flags using the Mixpanel Swift SDK. Feature Flags allow you to control the rollout of your features, conduct A/B testing, and manage application behavior without deploying new code.
For complete Swift SDK documentation, see the Swift SDK guide.
Prerequisites
Before implementing Feature Flags, ensure:
- You are on an Enterprise subscription plan and have the latest version of the SDK installed (minimum supported version is
v5.1.3). If not, please follow this doc to install the SDK. - You have your Project Token from your Mixpanel Project Settings
Flag Initialization
Initializing the SDK with feature flags enabled requires passing a FeatureFlagOptions configuration to MixpanelOptions. This enables making an outbound request to Mixpanel servers with the current user context.
The server will assign the user context to a variant for each feature flag according to how they are configured in the Mixpanel UX.
The response will include an assigned variant for each flag that the user context is in a rollout group for. If a flag is not returned, it most likely signifies that the user was either not in the rollout percentage for a flag or in the configured targeting cohort.
Example Usage
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: MixpanelOptions(
featureFlagOptions: FeatureFlagOptions(enabled: true)
))If your flag is configured with a Variant Assignment Key other than distinct_id or device_id for any of the feature flags in your project, then the call to initialize feature flags must include those keys.
For example, for a Variant Assignment Key, company_id, you would setup the SDK as follows:
let options = MixpanelOptions(
featureFlagOptions: FeatureFlagOptions(enabled: true, context: [
"company_id": "X"
])
)
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: options)If you are using Runtime Targeting in any of the feature flags in your project, then any properties that you use in targeting should be included in a customProperties node within the context:
let options = MixpanelOptions(
featureFlagOptions: FeatureFlagOptions(enabled: true, context: [
"company_id": "X",
"customProperties": [
"platform": "ios"
]
])
)
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: options)Flag Persistence
Minimum supported SDK version: v6.4.0
By default (networkOnly), the SDK fetches fresh flag assignments and waits for the network response before variant calls complete. You can enable persistence to persist assignments to UserDefaults so they are available immediately on subsequent launches.
Configure a variantLookupPolicy on FeatureFlagOptions:
networkFirst — The SDK waits for the network on every launch. If the network call fails, the persisted value is returned as a fallback. Async getter calls block until the fetch completes (or the fallback is resolved).
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: MixpanelOptions(
featureFlagOptions: FeatureFlagOptions(
enabled: true,
variantLookupPolicy: .networkFirst()
)
))
// getVariantValue waits for the network. Falls back to persisted value if network fails.
Mixpanel.mainInstance().flags.getVariantValue("my-feature-flag", fallbackValue: "control") { value in
// value is from the network response, or from persistence if the network was unavailable
}persistenceUntilNetworkSuccess — Persisted variants are returned immediately on launch. Each getter call while serving persisted data triggers a background network fetch (deduplicated — only one fetch runs at a time). If the fetch fails, persisted data continues to be served and the next getter call retries the fetch. Once a fetch succeeds, the in-memory state updates and subsequent calls no longer trigger background fetches.
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: MixpanelOptions(
featureFlagOptions: FeatureFlagOptions(
enabled: true,
variantLookupPolicy: .persistenceUntilNetworkSuccess()
)
))
// getVariantValue returns immediately from persistence. Each call triggers a background fetch
// until a network response succeeds and replaces the persisted state.
Mixpanel.mainInstance().flags.getVariantValue("my-feature-flag", fallbackValue: "control") { value in
// value is from persistence; background fetch retries on each call until network succeeds
}To customize the TTL (default: 24 hours), pass a TimeInterval:
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: MixpanelOptions(
featureFlagOptions: FeatureFlagOptions(
enabled: true,
variantLookupPolicy: .networkFirst(persistenceTtl: 12 * 3600) // 12 hours
)
))Variant source
Every MixpanelFlagVariant carries a source property indicating where the value came from: .network, .persistence(persistedAt:), or .fallback. Use getVariant() to receive the full variant object.
let fallback = MixpanelFlagVariant(key: "control", value: "control")
Mixpanel.mainInstance().flags.getVariant("my-feature-flag", fallback: fallback) { variant in
let value = variant.value
let source = variant.source // .network, .persistence(persistedAt:), or .fallback
}$experiment_started properties
When a variant is served, the $experiment_started exposure event includes:
$variant_source—"network"or"persistence"$persisted_at_in_ms— epoch ms when the variant set was persisted (persistence only)$ttl_in_ms— the configured TTL in ms (persistence only)
Persisted variants are tied to the current distinct_id. Calling identify() with a new ID or calling reset() clears persisted data and triggers a fresh fetch.
Flag Reload
Following initialization, you can reload feature flag assignments in a couple of ways:
- After a user logs in or out of your application and you call
identify, a feature flag reload will be triggered.
let updatedDistinctId = ""
Mixpanel.mainInstance().identify(distinctId: updatedDistinctId)- To refresh flag variants that may have changed during the lifetime of your app, you can manually reload flags.
Mixpanel.mainInstance().flags.loadFlags()- To update the feature flags context after initialization and trigger a reload with the new values, use
setContext. This completely replaces the previously set custom context.
Mixpanel.mainInstance().flags.setContext([
"company_id": "Y",
"customProperties": [
"platform": "ios"
]
]) {
// Flags have been re-fetched with the new context
}- Calling
reset()clears all feature flag assignments from memory and triggers a new fetch for the updateddistinct_id. Requires SDK versionv6.4.0or later.
Mixpanel.mainInstance().reset()
// Flags are cleared immediately. A new fetch begins for the anonymous distinct_id.Deferred Flag Loading
By default, feature flags are automatically fetched when the app first enters the foreground (prefetchFlags defaults to true). If your workflow requires calling identify() before the first flag fetch — for example, to ensure flags are evaluated against the correct user — you can defer automatic loading by setting prefetchFlags to false:
let options = MixpanelOptions(
token: "YOUR_TOKEN",
featureFlagOptions: FeatureFlagOptions(enabled: true, prefetchFlags: false)
)
Mixpanel.initialize(token: "YOUR_PROJECT_TOKEN", options: options)
// Flags will not be fetched until identify() or loadFlags() is called
Mixpanel.mainInstance().identify(distinctId: "user-123")When prefetchFlags is set to false, flags will not be loaded until you explicitly call identify() or Mixpanel.mainInstance().flags.loadFlags().
Flag Evaluation
Lookup the assigned value for a feature flag.
This action triggers tracking an exposure event, $experiment_started to your Mixpanel project.
Asynchronous Flag Variant Retrieval
Experiment Flags: Get Variant Value
// Get just the flag value asynchronously
Mixpanel.mainInstance().flags.getVariantValue("my-feature-flag", fallbackValue: "control") { value in
DispatchQueue.main.async {
// Use flag value in your application logic
if let stringValue = value as? String {
switch stringValue {
case "variant_a":
showExperienceForVariantA()
case "variant_b":
showExperienceForVariantB()
default:
showDefaultExperience()
}
}
}
}FeatureGates: Check if Flag is Enabled/Disabled
// Check if a boolean flag is enabled asynchronously
Mixpanel.mainInstance().flags.isEnabled("my-boolean-flag", fallbackValue: false) { isEnabled in
DispatchQueue.main.async {
if isEnabled {
showNewFeature()
} else {
showOldFeature()
}
}
}Synchronous Flag Variant Retrieval
Experiment Flags: Get Variant Value
// Get just the flag value synchronously
let flagValue = Mixpanel.mainInstance().flags.getVariantValueSync("my-feature-flag", fallbackValue: "control")
// Use flag value in your application logic
if flagValue as? String == "variant_a" {
showExperienceForVariantA()
} else if flagValue as? String == "variant_b" {
showExperienceForVariantB()
} else {
showDefaultExperience()
}Feature Gates: Check if Flag is Enabled/Disabled
// Check if a boolean flag is enabled
let isEnabled = Mixpanel.mainInstance().flags.isEnabledSync("my-boolean-flag", fallbackValue: false)
if isEnabled {
showNewFeature()
} else {
showOldFeature()
}Get All Flag Assignments
You can retrieve all feature flag assignments at once. These methods do not trigger $experiment_started tracking events.
Synchronous
getAllVariantsSync() returns a [String: MixpanelFlagVariant] dictionary containing all current flag assignments. If flags have not been loaded yet, it returns an empty dictionary.
let allFlags = Mixpanel.mainInstance().flags.getAllVariantsSync()
for (flagName, variant) in allFlags {
print("\(flagName): \(variant.value)")
}Asynchronous
getAllVariants(completion:) returns a [String: MixpanelFlagVariant] dictionary via a completion handler. If flags have not been loaded yet, it will attempt to fetch them before returning.
Mixpanel.mainInstance().flags.getAllVariants { allFlags in
for (flagName, variant) in allFlags {
print("\(flagName): \(variant.value)")
}
}Frequently Asked Questions
What if I’m not receiving any flags on SDK initialization?
- Check your project token:
- Ensure you’re using the correct project token from your Mixpanel project settings
- Review flag configuration:
- Make sure your feature flag is enabled
- Check the flag’s rollout percentage
- User contexts that are not assigned to the rollout percentage will not receive flags
- If you are using a targeting cohort, verify on the mixpanel ‘Users’ page that the user’s
distinct_idis a member of that cohort.
- Review SDK parameters:
- Ensure
FeatureFlagOptions(enabled: true)is passed toMixpanelOptionsvia thefeatureFlagOptionsparameter - If using a custom Variant Assignment Key, ensure it is included in the
FeatureFlagOptionscontextparameter - If using Runtime Targeting, ensure all properties used in targeting are included in the
customPropertiesobject within theFeatureFlagOptionscontext
- Check flags readiness: Use
areFlagsReady()to check if flags have been loaded before making synchronous calls - Enable debug mode: Set up logging to see detailed information about flag requests and responses
Was this page useful?