-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Bug Description
In most of the SDKs, the method to retrieve a feature flag payload is a standalone method. This method does not raise the $feature_flag_called event, which is a source of confusion for folks. The reason it doesn't is because we expect a pattern like so:
if is_feature_enabled('key'): # Raises event
payload = get_feature_flag_payload('key') # Does not raise eventIf get_feature_flag_payload raised the $feature_flag_called event, we'd double count here.
Problem
The problem is that get_feature_flag_payload(...) doesn't require that you call is_feature_enabled or get_feature_flag first. It can be called independently:
payload = get_feature_flag_payload('key')Every SDK except Unity/.NET follows this problematic pattern:
getFeatureFlag()→ raises$feature_flag_calledeventgetFeatureFlagPayload()→ NO event raised
Why customers complain: If they only call getFeatureFlagPayload(), they get zero analytics about feature flag usage. This is confusing because:
- The name suggests it's checking a feature flag
- They expect it to behave similarly to
getFeatureFlag() - There's no documentation making it clear this is a "silent" method
This sadly does not lead customers into the pit of success.
Solution
For each SDK, we should deprecate/obsolete the standalone method to retrieve the payload in favor of a pattern that leads into the pit of success.
Ideal API Pattern
Strongly-typed languages (Unity/.NET style):
flag = client.getFeatureFlag("my-flag", distinctId) // raises event, returns wrapper
flag.isEnabled // boolean
flag.variant // string or null
flag.getPayload<T>() // deserializes payload, NO additional event
- How it works:
GetFeatureFlag()returns aPostHogFeatureFlagwrapper that contains both value and payload - Event: Raised once when
GetFeatureFlag()is called - Payload: Accessed via
.GetPayload<T>()on the returned object - no additional event. Also accessible viaGetPayloadJson()which returns a dictionary style JSON object.
Dynamic languages:
result = client.get_feature_flag_result("my-flag", distinctId) // raises event
result.value // boolean or variant string
result.payload // the payload object
Note
This pattern exists today in posthog-python but I don't think its well known. I think we need to deprecate get_feature_flag_payload() with a message pointing people to use get_feature_flag_result.
Additional Context
Feature Flag Payload Catalog Across PostHog SDKs
| SDK | getFeatureFlag() raises event? |
getFeatureFlagPayload() raises event? |
Combined method? | Design Pattern |
|---|---|---|---|---|
| Python | ✅ Yes (default) | ❌ No | get_feature_flag_result() returns both |
Has the method you want |
| JS (Web) | ✅ Yes (default) | ❌ No | None | Separate calls |
| Node | ✅ Yes (default) | ❌ No | getAllFlagsAndPayloads() (no events, returns all flags) |
Separate calls |
| React Native | ✅ Yes (default) | ❌ No | useFeatureFlagWithPayload() hook (raises event via getFeatureFlag()) |
Hook is good, imperative API is not |
| iOS | ✅ Yes (if config enabled) | ❌ No | None | Separate calls |
| Android | ✅ Yes (default) | ❌ No | None | Separate calls |
| PHP | ✅ Yes (default) | ❌ No | None | Separate calls |
| Ruby | ✅ Yes (default) | ❌ No | get_all_flags_and_payloads() (no events) |
Separate calls |
| Go | ✅ Yes (default) | ❌ No | None | Separate calls |
| .NET | ✅ Yes (default) | N/A - payload in FeatureFlag object |
GetFeatureFlagAsync() returns both |
Good pattern |
| Unity | ✅ Yes (if config enabled) | N/A - accessed via GetPayload<T>() |
GetFeatureFlag().GetPayload<T>() |
Best pattern |
| Flutter | ✅ Yes (delegates to native) | ❌ No (delegates to native) | None | Separate calls |
| Elixir | ✅ Yes (check/3 only) |
N/A - no dedicated method | None | Manual extraction needed |
Migration Path
| SDK | Current State | Migration Path |
|---|---|---|
| Python | get_feature_flag_result() exists |
Deprecate get_feature_flag_payload() with warning pointing to get_feature_flag_result() |
| JS (Web) | No combined method | Add getFeatureFlagResult(), then deprecate getFeatureFlagPayload() |
| Node | No single-flag combined method | Add getFeatureFlagResult(), then deprecate getFeatureFlagPayload() |
| React Native | Hook is good, imperative API is not | Keep useFeatureFlagWithPayload() hook. Add getFeatureFlagResult() for imperative usage. Deprecate getFeatureFlagPayload() |
| iOS | No combined method | Add method returning struct with value and payload, then deprecate getFeatureFlagPayload() |
| Android | No combined method | Add method returning object with value and payload, then deprecate getFeatureFlagPayload() |
| PHP | No combined method | Add get_feature_flag_result(), then deprecate get_feature_flag_payload() |
| Ruby | No combined method | Add get_feature_flag_result(), then deprecate get_feature_flag_payload() |
| Go | No combined method | Add GetFeatureFlagResult(), then deprecate GetFeatureFlagPayload() |
| Flutter | Delegates to native | Wait for iOS/Android fixes, then update bindings and deprecate getFeatureFlagPayload() |
| Elixir | No payload method exists | Add get_feature_flag_result/3 that wraps check/3 and includes payload |
| .NET | Already good | No changes needed |
| Unity | Already good | No changes needed |
Debug info
- [ ] PostHog Cloud, Debug information: [please copy/paste from https://us.posthog.com/settings/project-details#variables or https://eu.posthog.com/settings/project-details#variables]
- [ ] PostHog Hobby self-hosted with `docker compose`, version/commit: [please provide]
- [ ] PostHog self-hosted with Kubernetes (deprecated, see [`Sunsetting Kubernetes support`](https://posthog.com/blog/sunsetting-helm-support-posthog)), version/commit: [please provide]Metadata
Metadata
Assignees
Labels
Type
Projects
Status