From 94594a677b3178cb9d928cef33cff8546794ed28 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Wed, 3 Jun 2026 12:12:14 +0100 Subject: [PATCH 1/2] Document direct-value threshold feature flags --- docs/remote-feature-flags.md | 67 ++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/docs/remote-feature-flags.md b/docs/remote-feature-flags.md index 47181f9..89fa2eb 100644 --- a/docs/remote-feature-flags.md +++ b/docs/remote-feature-flags.md @@ -83,6 +83,60 @@ Choose the appropriate feature flag type based on your needs: ] ``` +Threshold entries without `thresholdVersion` use the legacy output shape. After the controller selects the matching threshold entry, the processed flag is: + +```json +{ + "name": "feature is ON", + "value": true +} +``` + +Use this shape when selectors read the selected flag data from `.value`. + +#### Direct-value threshold entries + +Use `thresholdVersion: 2` when a threshold-based flag should expose the selected `value` directly, matching the shape of a non-threshold object flag. This is useful when converting an existing object flag to threshold rollout and existing selectors read root-level fields such as `flag.enabled`. + +```json +[ + { + "thresholdName": "feature is ON", + "thresholdVersion": 2, + "scope": { + "type": "threshold", + "value": 0.3 + }, + "value": { + "enabled": true, + "minimumVersion": "13.10.0" + } + }, + { + "thresholdName": "feature is OFF", + "thresholdVersion": 2, + "scope": { + "type": "threshold", + "value": 1 + }, + "value": { + "enabled": false + } + } +] +``` + +If the first entry is selected, the processed flag is: + +```json +{ + "enabled": true, + "minimumVersion": "13.10.0" +} +``` + +Existing non-threshold object flags do not need `thresholdVersion`. This property is only used on entries inside threshold arrays. + The distribution is deterministic based on the user's `metametricsId`, ensuring consistent group assignment across sessions. **3. Object flag with version-based scope**: Use for: @@ -112,6 +166,8 @@ In this example: users get different UI configurations based on their app versio The version matching is deterministic and uses semantic version comparison to find the highest version that is less than or equal to the current app version. +`thresholdVersion` is not used for this flag type. The selected version value is returned as-is. + **4. Composing version-based scope with threshold scope**: Use when you need to control both version targeting and user percentage rollout simultaneously. In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% of users on v13.2.0+. @@ -122,7 +178,8 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% "versions": { "13.0.0": [ { - "name": "gradual rollout", + "thresholdName": "gradual rollout", + "thresholdVersion": 2, "scope": { "type": "threshold", "value": 0.3 @@ -130,7 +187,8 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% "value": { "enabled": true } }, { - "name": "disabled", + "thresholdName": "disabled", + "thresholdVersion": 2, "scope": { "type": "threshold", "value": 1 @@ -140,7 +198,8 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% ], "13.2.0": [ { - "name": "full rollout", + "thresholdName": "full rollout", + "thresholdVersion": 2, "scope": { "type": "threshold", "value": 1 @@ -158,6 +217,8 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% - v13.2.1 user (any bucket) → `{ "enabled": true }` (100% rollout) - v12.9.0 user → Feature excluded (no matching versions) +When composing version-based and threshold scopes, place `thresholdVersion: 2` inside each threshold entry under the relevant version. Without `thresholdVersion: 2`, the selected threshold entry uses the legacy `{ "name": "...", "value": ... }` wrapper shape. + ## Implementation Guide ### 1. Creating feature flag in LaunchDarkly. From 6131eb606099bdefa7278a3249d787db49a48446 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Fri, 5 Jun 2026 09:32:15 +0100 Subject: [PATCH 2/2] Explain thresholdName for direct-value flags --- docs/remote-feature-flags.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/remote-feature-flags.md b/docs/remote-feature-flags.md index 89fa2eb..1c7c4f6 100644 --- a/docs/remote-feature-flags.md +++ b/docs/remote-feature-flags.md @@ -98,6 +98,8 @@ Use this shape when selectors read the selected flag data from `.value`. Use `thresholdVersion: 2` when a threshold-based flag should expose the selected `value` directly, matching the shape of a non-threshold object flag. This is useful when converting an existing object flag to threshold rollout and existing selectors read root-level fields such as `flag.enabled`. +When using `thresholdVersion: 2`, use `thresholdName` instead of `name` to label the threshold entry. `thresholdName` is metadata for configuration readability and is not included in the processed feature flag value. Because the selected entry resolves directly to its `value`, this avoids confusion or collisions with any `name` field inside the value object itself. `thresholdName` is recommended for clarity, but the controller selects entries using `scope` and returns `value`. + ```json [ {