From 070353ca79ee7fd7496cee26de4189c57e2adca4 Mon Sep 17 00:00:00 2001 From: sallem-consensys Date: Thu, 2 Jul 2026 10:26:44 -0400 Subject: [PATCH] docs: update remote feature flag threshold API Co-authored-by: Cursor --- docs/remote-feature-flags.md | 53 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/docs/remote-feature-flags.md b/docs/remote-feature-flags.md index 1c7c4f6..a24fbf1 100644 --- a/docs/remote-feature-flags.md +++ b/docs/remote-feature-flags.md @@ -83,28 +83,31 @@ 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: +After the controller selects the matching threshold entry, `remoteFeatureFlags[flagName]` contains the entry's `value` directly. For example, if the first entry above is selected: ```json -{ - "name": "feature is ON", - "value": true -} +true +``` + +When the selected threshold entry includes `name`, the controller stores the group label separately on controller state in `featureFlagThresholdGroups`: + +```typescript +remoteFeatureFlags.myThresholdFlag; // true +featureFlagThresholdGroups.myThresholdFlag; // "feature is ON" ``` -Use this shape when selectors read the selected flag data from `.value`. +Read flag payloads from `remoteFeatureFlags`. Read A/B group assignment from `featureFlagThresholdGroups` when the selected entry includes `name`. -#### Direct-value threshold entries +#### Object-valued 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`. +Threshold entries can return object values 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`. +Use `name` to label each threshold entry. The selected entry's `value` is returned directly from `remoteFeatureFlags`, and the selected entry's `name` is stored separately in `featureFlagThresholdGroups`. ```json [ { - "thresholdName": "feature is ON", - "thresholdVersion": 2, + "name": "feature is ON", "scope": { "type": "threshold", "value": 0.3 @@ -115,8 +118,7 @@ When using `thresholdVersion: 2`, use `thresholdName` instead of `name` to label } }, { - "thresholdName": "feature is OFF", - "thresholdVersion": 2, + "name": "feature is OFF", "scope": { "type": "threshold", "value": 1 @@ -137,7 +139,12 @@ If the first entry is selected, the processed flag is: } ``` -Existing non-threshold object flags do not need `thresholdVersion`. This property is only used on entries inside threshold arrays. +The selected group label is available separately: + +```typescript +remoteFeatureFlags.myObjectThresholdFlag; // { enabled: true, minimumVersion: "13.10.0" } +featureFlagThresholdGroups.myObjectThresholdFlag; // "feature is ON" +``` The distribution is deterministic based on the user's `metametricsId`, ensuring consistent group assignment across sessions. @@ -168,7 +175,7 @@ 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. +Threshold entries are 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. @@ -180,8 +187,7 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% "versions": { "13.0.0": [ { - "thresholdName": "gradual rollout", - "thresholdVersion": 2, + "name": "gradual rollout", "scope": { "type": "threshold", "value": 0.3 @@ -189,8 +195,7 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% "value": { "enabled": true } }, { - "thresholdName": "disabled", - "thresholdVersion": 2, + "name": "disabled", "scope": { "type": "threshold", "value": 1 @@ -200,8 +205,7 @@ In this example: the feature is rolled out to 30% of users on v13.0.0+, and 100% ], "13.2.0": [ { - "thresholdName": "full rollout", - "thresholdVersion": 2, + "name": "full rollout", "scope": { "type": "threshold", "value": 1 @@ -219,7 +223,7 @@ 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. +When composing version-based and threshold scopes, place threshold entries under the relevant version. Processed threshold values are returned directly from `remoteFeatureFlags`; use `featureFlagThresholdGroups` for group names when entries include `name`. ## Implementation Guide @@ -307,10 +311,7 @@ In production, MetaMask Extension fetches remote flags from the [client-config A { "_flags": { "remoteFeatureFlags": { - "testFlagForThreshold": { - "name": "test-flag", - "value": "121212" - } + "testFlagForThreshold": true } } }