Client-side PostHog libraries (like posthog-js
) are able to have a rather simple feature flags implementation, given that they only ever have to deal with one user. As such, we can make a request at the start of a session to fetch feature flags that are enabled for a user, keep those on memory, and periodically check for updates.
On server-side libraries, however, we need to be able to handle n distinct IDs. As such, we are unable to fetch user-specific flags proactively, since there is an infinite number of possible distinct IDs that one can call isFeatureEnabled
with. As a result, we've developed an approach to implementing feature flags on our server-side libraries, described in this doc.
A note: simple flags
To improve performance of feature flag methods on the server, all PostHog feature flags have a property is_simple_flag
. A simple flag is one that does not rely on any filters, so calculating if it is on for a given user or not depends entirely on two things: rollout percentage and user distinct ID.
Distinct ID is passed in by the user, so all we need is the rollout percentage to determine if a flag is on without having to offload the calculation to the PostHog instance.
Pseudocode Implementation
class FeatureFlagsPoller:constructor():# Check if there's a Personal API Key available in addition to the Project API Keypoll() -> void:# 1. Set up a poller to fetch a list of available flags from api/feature_flag using the Personal API Key# 2. Store the resultisFeatureEnabled(key: string, distinctId: string, defaultValue: boolean) -> boolean:flag = flagObjectOrNull(key)if !flag:return defaultValueresult = defaultValueif flag['is_simple_flag']:result = isSimpleFlagEnabled(key, distinctId, flag['rollout_percentage'])else:# Send a POST request to /decide passing the user distinct_id in the request data# Authenticate with the Project API Keyresponse = post('/decide/', data={ 'distinct_id': distinctId })result = key in response.data.featureFlags# Capture a posthog event called $feature_flag_called, passing the properties $feature_flag (the key) and $feature_flag_responseclient.capture('$feature_flag_called', { '$feature_flag': key, '$feature_flag_response': result })return resultisSimpleFlagEnabled(key: string, distinctId: string, rolloutPercentage: int) -> boolean:if (!rolloutPercentage):return true# 1. Get the SHA1 hash of the a string in the following format: key.distinctId# 2. Convert it to hex format and take the first 15 chars# 3. Cast it to an integer from base16# 4. Divide it by the following hexadecimal number: 0xfffffffffffffff (1152921504606847000)# 5. Return: (hashToInteger / 0xfffffffffffffff) <= (rolloutPercentage / 100)
Ensuring consistency with isSimpleFlagEnabled
To check if your isSimpleFlag
implementation is in accordance with others, the following should be true:
SHA1('a.b')
should equal'69f6642c9d71b463485b4faf4e989dc3fe77a8c6'
- The floating point value for the hash of
'a.b'
should equal0.4139158829615955