Skip to content

chore(sdks): Normalize handling of feature flag payloads. #43520

@haacked

Description

@haacked

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 event

If 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:

  1. getFeatureFlag() → raises $feature_flag_called event
  2. getFeatureFlagPayload()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 a PostHogFeatureFlag wrapper 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 via GetPayloadJson() 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

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions