PostHog.FeatureFlags (posthog v2.15.0)

Copy Markdown View Source

Convenience functions to work with Feature Flags API

Summary

Functions

Checks feature flag and returns the variant or raises on error.

Evaluates feature flags for a distinct_id and returns a snapshot.

Make request to /flags API.

Gets the full feature flag result including value and payload.

Gets the full feature flag result or raises on error.

Copies a snapshot's $feature/<key> and $active_feature_flags properties into the default per-process PostHog context.

Copies a snapshot's $feature/<key> and $active_feature_flags properties into a named PostHog instance's per-process context.

Functions

check(name \\ PostHog, flag_name, distinct_id_or_body \\ nil)

This function is deprecated. Use PostHog.FeatureFlags.evaluate_flags/2 with PostHog.FeatureFlags.Evaluations.enabled?/2 or get_flag/2.
@spec check(
  PostHog.supervisor_name(),
  String.t(),
  PostHog.distinct_id() | map() | nil
) ::
  {:ok, boolean()} | {:ok, String.t()} | {:error, Exception.t()}

Checks feature flag

Deprecated

Use PostHog.FeatureFlags.evaluate_flags/2 plus PostHog.FeatureFlags.Evaluations.enabled?/2 or PostHog.FeatureFlags.Evaluations.get_flag/2 instead. The snapshot lets one /flags call serve multiple flag checks plus event enrichment.

If there is a variant assigned, returns {:ok, variant}. Otherwise, {:ok, true} or {:ok, false}.

Accepts an optional distinct_id or a map with request body. If neither is passed, attempts to read distinct_id from the context.

This function will also send $feature_flag_called event and set $feature/feature-flag-name property in context.

Examples

Check boolean feature flag for distinct_id:

iex> PostHog.FeatureFlags.check("example-feature-flag-1", "user123")
{:ok, true}

Check multivariant feature flag for distinct_id in the current context:

iex> PostHog.set_context(%{distinct_id: "user123"})
iex> PostHog.FeatureFlags.check("example-feature-flag-1")
{:ok, "variant1"}

Check boolean feature flag through a named PostHog instance:

PostHog.FeatureFlags.check(MyPostHog, "example-feature-flag-1", "user123")

check!(name \\ PostHog, flag_name, distinct_id_or_body \\ nil)

This function is deprecated. Use PostHog.FeatureFlags.evaluate_flags/2 with PostHog.FeatureFlags.Evaluations.enabled?/2 or get_flag/2.
@spec check!(
  PostHog.supervisor_name(),
  String.t(),
  PostHog.distinct_id() | map() | nil
) ::
  boolean() | String.t() | no_return()

Checks feature flag and returns the variant or raises on error.

Deprecated

Use PostHog.FeatureFlags.evaluate_flags/2 and PostHog.FeatureFlags.Evaluations.enabled?/2 / PostHog.FeatureFlags.Evaluations.get_flag/2 instead.

This is a wrapper around check/3 that returns the variant directly or raises an exception on error. This follows the Elixir convention where functions ending with ! raise exceptions instead of returning error tuples.

Warning: Use this function with care as it will raise an error if the feature flag is not found or if there are any API errors. For more resilient code, use check/3 which returns {:error, reason} instead of raising.

Examples

Check feature flag and get the variant:

iex> PostHog.FeatureFlags.check!("example-feature-flag-1", "user123")
true

Check multivariant feature flag for distinct_id in current context:

iex> PostHog.set_context(%{distinct_id: "user123"})
iex> PostHog.FeatureFlags.check!("example-feature-flag-1")
"variant1"

Check feature flag through a named PostHog instance:

iex> PostHog.FeatureFlags.check!(MyPostHog, "example-feature-flag-1", "user123")
false

Raises an error when feature flag is not found:

iex> PostHog.FeatureFlags.check!("example-feature-flag-3", "user123")
** (PostHog.UnexpectedResponseError) Feature flag example-feature-flag-3 was not found in the response

evaluate_flags(name \\ PostHog, distinct_id_or_body \\ nil)

@spec evaluate_flags(PostHog.supervisor_name(), PostHog.distinct_id() | map() | nil) ::
  {:ok, PostHog.FeatureFlags.Evaluations.t()} | {:error, Exception.t()}

Evaluates feature flags for a distinct_id and returns a snapshot.

Returns {:ok, %PostHog.FeatureFlags.Evaluations{}} on success. Definitions are evaluated locally first when privileged local evaluation is configured; unresolved flags are filled by at most one /flags call. If that fallback fails after some flags resolved locally, the successful local subset is returned as a partial snapshot. If nothing resolved, the remote error is returned to preserve the existing {:error, reason} contract. The snapshot lets you branch on multiple flags and enrich captured events — see PostHog.FeatureFlags.Evaluations for the full snapshot API and set_in_context/2 for the recommended capture-enrichment flow.

Accepts an optional distinct_id or a request body map. If neither is passed, attempts to read distinct_id from the context.

Body options

When passing a map, the following keys are forwarded to the /flags request body unchanged:

  • :distinct_id (required, unless found in context)
  • :groups
  • :person_properties
  • :group_properties
  • :device_id - alternate bucketing identifier for device-bucketed flags

The public :disable_geoip option is sent under the /flags wire key geoip_disable.

Plus these snapshot-specific options:

  • :only_evaluate_locally - when true, never calls /flags; unresolved flags are omitted from the snapshot.

  • :flag_keys - list of flag keys. A non-empty list is forwarded to the request as flag_keys_to_evaluate so the server returns only those flags. An empty list returns an empty snapshot without making a request. An omitted or nil value evaluates all flags through the normal request path. This scopes the network response, distinct from PostHog.FeatureFlags.Evaluations.only/2 which filters an already-fetched snapshot in memory. Clean remote omissions for explicitly requested keys are retained in a bounded per-instance set until definitions refresh, so repeated and concurrent missing-key probes avoid duplicate requests.

Examples

Evaluate flags for a distinct_id:

{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("user123")
PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "new-dashboard")

Evaluate a scoped set of flags with person properties:

PostHog.FeatureFlags.evaluate_flags(%{
  distinct_id: "user123",
  person_properties: %{plan: "enterprise"},
  flag_keys: ["new-dashboard", "beta-checkout"]
})

Evaluate through a named PostHog instance:

PostHog.FeatureFlags.evaluate_flags(MyPostHog, "user123")

flags(name \\ PostHog, body)

Make request to /flags API.

This function is a thin wrapper over a client call and is useful as a building block to build your own check/3. For example, this is a preferred way to access remote config payload.

Examples

Make request to /flags API:

PostHog.FeatureFlags.flags(%{distinct_id: "user123"})

Make request to /flags API with additional body params:

PostHog.FeatureFlags.flags(%{distinct_id: "my_distinct_id", groups: %{group_type: "group_id"}})

Make request to /flags API through a named PostHog instance:

PostHog.FeatureFlags.flags(MyPostHog, %{distinct_id: "user123"})

flags_for(name \\ PostHog, distinct_id_or_body \\ nil)

@spec flags_for(PostHog.supervisor_name(), PostHog.distinct_id() | map() | nil) ::
  {:ok, map()} | {:error, Exception.t()}

Get all feature flags.

Accepts an optional distinct_id or a map with request body. If neither is passed, attempts to read distinct_id from the context.

Examples

Get all feature flags:

PostHog.FeatureFlags.flags_for("user123")

Get all feature flags with full request body:

PostHog.FeatureFlags.flags_for(%{distinct_id: "user123", groups: %{group_type: "group_id"}})

Get all feature flags for distinct_id from the context:

PostHog.set_context(%{distinct_id: "user123"})
PostHog.FeatureFlags.flags_for()

Get all feature flags through a named PostHog instance:

PostHog.FeatureFlags.flags_for(MyPostHog, "foo")

get_feature_flag_result(name \\ PostHog, flag_name, distinct_id_or_body \\ nil, opts \\ [])

This function is deprecated. Use PostHog.FeatureFlags.evaluate_flags/2 with PostHog.FeatureFlags.Evaluations.
@spec get_feature_flag_result(
  PostHog.supervisor_name(),
  String.t(),
  PostHog.distinct_id() | map() | nil,
  keyword()
) :: {:ok, PostHog.FeatureFlags.Result.t() | nil} | {:error, Exception.t()}

Gets the full feature flag result including value and payload.

Deprecated

Use PostHog.FeatureFlags.evaluate_flags/2 and access flags from the returned PostHog.FeatureFlags.Evaluations snapshot. The snapshot exposes the same metadata (id, version, reason, payload) plus filter helpers and capture enrichment via set_in_context/2.

Returns {:ok, %PostHog.FeatureFlags.Result{}} on success, {:ok, nil} if the flag is not found, or {:error, reason} on failure.

The PostHog.FeatureFlags.Result struct contains:

  • key - The flag name
  • enabled - Whether the flag is enabled
  • variant - The variant string (nil for boolean flags)
  • payload - The JSON payload configured for the flag (nil if not set)

By default, this function will send a $feature_flag_called event and set the $feature/feature-flag-name property in context.

Options

  • :send_event - Whether to send the $feature_flag_called event. Defaults to true.

Examples

Get feature flag result for distinct_id:

iex> PostHog.FeatureFlags.get_feature_flag_result("example-feature-flag-1", "user123")
{:ok, %PostHog.FeatureFlags.Result{key: "example-feature-flag-1", enabled: true, variant: nil, payload: nil}}

Get feature flag result with payload:

iex> PostHog.FeatureFlags.get_feature_flag_result("feature-with-payload", "user123")
{:ok, %PostHog.FeatureFlags.Result{key: "feature-with-payload", enabled: true, variant: "variant1", payload: %{"key" => "value"}}}

Get feature flag result without sending event:

iex> PostHog.FeatureFlags.get_feature_flag_result("my-flag", "user123", send_event: false)
{:ok, %PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}}

Flag not found returns {:ok, nil}:

iex> PostHog.FeatureFlags.get_feature_flag_result("non-existent-flag", "user123")
{:ok, nil}

Get feature flag result for distinct_id in the current context:

iex> PostHog.set_context(%{distinct_id: "user123"})
iex> PostHog.FeatureFlags.get_feature_flag_result("example-feature-flag-1")
{:ok, %PostHog.FeatureFlags.Result{key: "example-feature-flag-1", enabled: true, variant: nil, payload: nil}}

Get feature flag result through a named PostHog instance:

PostHog.FeatureFlags.get_feature_flag_result(MyPostHog, "example-feature-flag-1", "user123")

get_feature_flag_result!(name \\ PostHog, flag_name, distinct_id_or_body \\ nil, opts \\ [])

This function is deprecated. Use PostHog.FeatureFlags.evaluate_flags/2 with PostHog.FeatureFlags.Evaluations.
@spec get_feature_flag_result!(
  PostHog.supervisor_name(),
  String.t(),
  PostHog.distinct_id() | map() | nil,
  keyword()
) :: PostHog.FeatureFlags.Result.t() | nil | no_return()

Gets the full feature flag result or raises on error.

Deprecated

Use PostHog.FeatureFlags.evaluate_flags/2 and access flags from the returned PostHog.FeatureFlags.Evaluations snapshot.

This is a wrapper around get_feature_flag_result/4 that returns the result directly or raises an exception on error. This follows the Elixir convention where functions ending with ! raise exceptions instead of returning error tuples.

Returns nil if the flag is not found (does not raise), consistent with other PostHog SDKs.

Warning: Use this function with care as it will raise an error if there are any API errors (e.g. missing distinct_id). For more resilient code, use get_feature_flag_result/4 which returns {:error, reason} instead of raising.

Options

  • :send_event - Whether to send the $feature_flag_called event. Defaults to true.

Examples

Get feature flag result for distinct_id:

iex> PostHog.FeatureFlags.get_feature_flag_result!("example-feature-flag-1", "user123")
%PostHog.FeatureFlags.Result{key: "example-feature-flag-1", enabled: true, variant: nil, payload: nil}

Returns nil when flag is not found:

iex> PostHog.FeatureFlags.get_feature_flag_result!("non-existent-flag", "user123")
nil

Raises an error when distinct_id is missing:

iex> PostHog.FeatureFlags.get_feature_flag_result!("example-feature-flag-1")
** (PostHog.Error) distinct_id is required but wasn't explicitly provided or found in the context

set_in_context(snapshot)

@spec set_in_context(PostHog.FeatureFlags.Evaluations.t()) :: :ok

Copies a snapshot's $feature/<key> and $active_feature_flags properties into the default per-process PostHog context.

Parameters

Returns

Returns :ok.

Remarks

Any subsequent PostHog.capture/3 from this process automatically attaches these properties to the captured event — no additional /flags request, with the values guaranteed to match what the snapshot already evaluated.

For one-off enrichment without touching context, merge PostHog.FeatureFlags.Evaluations.event_properties/1 into a capture's properties directly.

Examples

{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("user123")
PostHog.FeatureFlags.set_in_context(snapshot)

# All subsequent captures pick up $feature/* and $active_feature_flags
PostHog.capture("page_viewed", %{distinct_id: "user123"})

set_in_context(name, snapshot)

Copies a snapshot's $feature/<key> and $active_feature_flags properties into a named PostHog instance's per-process context.

Parameters

Returns

Returns :ok.

Remarks

This is the named-instance variant of set_in_context/1.