Canonical guide for evaluating remote feature flags that include a minimum app version. Applies to all teams (Ramp, Perps, Money, Predict, platform selectors, etc.).
| Concern | Location |
|---|---|
| Version comparison + flag validation | app/util/remoteFeatureFlag/index.ts |
| Raw flag storage | RemoteFeatureFlagController.remoteFeatureFlags (no client version gating) |
| Boolean for UI / hooks | Selectors under app/selectors/featureFlagController/ or **/selectors/featureFlags/ |
Always use validatedVersionGatedFeatureFlag in selectors. Never duplicate hasMinimumRequiredVersion or validatedVersionGatedFeatureFlag in feature modules.
hasMinimumRequiredVersion is a lower-level helper used inside validatedVersionGatedFeatureFlag. Import it from the util only when you need a standalone version check — do not reimplement it.
LaunchDarkly JSON:
{
"enabled": true,
"minimumVersion": "7.65.0"
}Progressive-rollout wrappers ({ name, value: { enabled, minimumVersion } }) are normalized automatically.
Some flags use different property names (e.g. depositConfig uses active instead of enabled). Map to the canonical shape before calling the helper:
validatedVersionGatedFeatureFlag({
enabled: depositConfig.active ?? false,
minimumVersion: depositConfig.minimumVersion ?? '',
}) ?? false;RemoteFeatureFlagController resolves flags shaped as { versions: { "7.53.0": value, "7.60.0": value } } at fetch time using clientVersion. Consumers read the processed value from state — no validatedVersionGatedFeatureFlag call needed for that shape.
import { createSelector } from 'reselect';
import { selectRemoteFeatureFlags } from '../index'; // adjust path
import {
validatedVersionGatedFeatureFlag,
type VersionGatedFeatureFlag,
} from '../../../util/remoteFeatureFlag';
export const selectMyFeatureEnabled = createSelector(
selectRemoteFeatureFlags,
(remoteFeatureFlags) => {
const localFlag = process.env.MM_MY_FEATURE_ENABLED === 'true';
const remoteFlag =
remoteFeatureFlags?.myFeature as unknown as VersionGatedFeatureFlag;
return validatedVersionGatedFeatureFlag(remoteFlag) ?? localFlag;
},
);- Remote flag wins when valid;
undefinedfrom the helper triggers env/local fallback. - When
OVERRIDE_REMOTE_FEATURE_FLAGS=true, the helper returnsundefinedso local overrides apply.
const isEnabled = useSelector(selectMyFeatureEnabled);Do not call getVersion(), compare-versions, or local version helpers in hooks/components.
validatedVersionGatedFeatureFlag compares against the native binary version from react-native-device-info's getVersion() — not package.json. After a version bump, do a clean native rebuild if gating behaves unexpectedly.
- Local
function hasMinimumRequiredVersion(...)copies - Inline
compareVersions.compare(getVersion(), minimumVersion, '>=')for feature flags - Duplicate util files under
app/components/UI/**/utils/orapp/core/redux/slices/**/
- Perps feature flags — team-specific examples
- Compliance OFAC gating — real selector using this pattern
.cursor/rules/version-gated-feature-flags.mdc— Cursor agent rule- External: MetaMask remote feature flags