Feature Flags

How feature flags and rollouts work — the FeatureManager

In the previous concept on ExperienceManager, we learned how the SDK manages A/B tests (or "experiences") where visitors are assigned to different variations. But what if you don't need multiple variations? What if you just want to switch a specific piece of functionality on or off for certain users, like rolling out a new beta feature?

This is where Feature Flags come in, and the SDK has a dedicated manager for them.

The Problem: Toggling Features On and Off

Imagine you've developed a cool new "Dark Mode" for your website. You're not ready to release it to everyone yet. You want to:

  1. Enable it only for internal testers first.
  2. Later, enable it for 10% of all visitors.
  3. Eventually, roll it out to everyone.
  4. Maybe pass some configuration data (like the exact dark theme color) along with the feature flag.

Doing this with a full A/B test (ExperienceManager) feels like overkill. You don't have multiple variations of Dark Mode; you just want to turn it on or off. You need a simpler way to control the availability of this feature.

What is FeatureManager? Your Feature Control Panel

Think of the FeatureManager as the control panel or switchboard for your website's features. While the ExperienceManager is directing complex A/B tests with different variations, the FeatureManager is focused on the simpler task of flipping switches: turning individual features on or off for specific visitors.

Key jobs of the FeatureManager:

  1. Knows the Features: It can list all features defined in your Convert project (via methods like getList() and getFeature()).
  2. Checks the Switch: It can tell you if a specific feature (like "dark-mode") should be enabled for a particular visitor (via methods like isFeatureEnabled() and runFeature()).
  3. Provides Configuration: If a feature is enabled, it can also give you any configuration variables associated with it (e.g., the themeColor for Dark Mode).
  4. Uses Underlying Logic: Just like the ExperienceManager, it doesn't make decisions in isolation. It relies on the underlying system (especially the DataManager) to figure out if a visitor meets the rules and bucketing criteria set up in Convert experiences that might enable this feature.

In essence, FeatureManager lets you ask: "For this visitor, is feature X turned on?".

Using the FeatureManager (via Context)

Similar to experiences, you typically won't use the FeatureManager directly. You'll use the convenient methods provided by the visitor's Context object.

Let's check if a visitor should get the "Dark Mode" feature. Assume the feature is identified by the key 'dark-mode' in your Convert project.

  1. Get the Context: First, ensure you have the context for your visitor by calling createContext on your initialized SDK instance, passing the visitor ID and any known attributes.

  2. Check if the Feature is Enabled: Call isFeatureEnabled on the context, passing the feature key (e.g., 'dark-mode'). This returns a simple boolean: true if the feature should be active for this visitor, false otherwise.

  3. Getting Feature Details (including variables): If you need more than just on/off, like configuration values, call runFeature on the context, passing the feature key and optionally a set of bucketing attributes (visitor properties, location properties, etc.). The attributes are important if your feature targeting rules depend on them (like "only enable for users in Canada").

    The call returns a BucketedFeature object with the following properties:

    PropertyDescription
    featureIdThe feature's unique ID
    featureKeyThe feature's key (e.g., 'dark-mode')
    statusEither enabled or disabled
    variablesKey-value pairs of feature variables (when enabled)
    experienceKeyThe key of the experience that enabled this feature (when enabled)

    If enabled, the result contains status: enabled and a variables map (e.g., { themeColor: '#111', fontSize: '16px' }).

    If disabled, the result contains status: disabled.

Under the Hood: How runFeature (and isFeatureEnabled) Works

How does the SDK decide if 'dark-mode' is enabled for 'user123'? It's a bit different from A/B testing. A feature isn't usually tested in isolation; it's often enabled as part of one or more experiences.

  1. Call Context Method: Your code calls runFeature('dark-mode', ...) on the visitor context.
  2. Delegate to FeatureManager: The Context object passes the request to the FeatureManager's runFeature method, supplying the visitor ID and bucketing attributes.
  3. FeatureManager Orchestrates: The FeatureManager doesn't do bucketing itself. Instead, it needs to find out if the visitor is bucketed into any variation of any relevant experience that happens to enable the 'dark-mode' feature. It does this using its runFeatures helper method.
  4. Call runFeatures: The runFeature method calls the internal runFeatures method, filtering to only consider experiences linked to 'dark-mode'.
  5. runFeatures Logic:
    • Get Relevant Experiences: It asks the DataManager for the list of experiences that might affect the 'dark-mode' feature (or all experiences if not filtered).
    • Check Each Experience: For each relevant experience, it asks the DataManager to perform bucketing for 'user123'. This involves the RuleManager and BucketingManager we saw in the previous concept.
    • Examine Variation: If the visitor is bucketed into a variation for an experience, runFeatures inspects that variation's changes data.
    • Feature Enabled? It checks if the variation's changes explicitly list the 'dark-mode' feature (feature_id) as a FULLSTACK_FEATURE change type.
    • Collect Results: If it finds any variation (across all checked experiences) that enables 'dark-mode' for 'user123', it gathers the feature details (key, status='enabled', variables).
  6. Return Result: runFeatures returns a list of enabled features. runFeature picks the relevant one ('dark-mode') from this list. If 'dark-mode' wasn't found in any enabled variation's changes, it returns the disabled status object.
  7. Context Returns: The Context returns the final BucketedFeature object to your code.

Here's a simplified diagram:

sequenceDiagram
    participant YourCode as Your Application Code
    participant Context as Visitor Context
    participant FeatureMgr as FeatureManager
    participant DataMgr as DataManager
    participant BucketingEtc as Bucketing/Rules

    YourCode->>Context: runFeature('dark-mode', ...)
    activate Context
    Context->>FeatureMgr: runFeature('user123', 'dark-mode', ...)
    activate FeatureMgr
    FeatureMgr->>FeatureMgr: runFeatures('user123', ..., features: ['dark-mode'])
    FeatureMgr->>DataMgr: Get relevant experiences for 'dark-mode'
    activate DataMgr
    DataMgr-->>FeatureMgr: List of Experiences (e.g., Exp1, Exp2)
    deactivate DataMgr
    loop For Each Experience (Exp1, Exp2)
        FeatureMgr->>DataMgr: getBucketing('user123', Exp.key, ...)
        activate DataMgr
        DataMgr->>BucketingEtc: Check rules & perform bucketing
        activate BucketingEtc
        BucketingEtc-->>DataMgr: Return Variation (or RuleError)
        deactivate BucketingEtc
        DataMgr-->>FeatureMgr: Return Variation (e.g., VarA for Exp1)
        deactivate DataMgr
        Note over FeatureMgr: Inspect VarA's changes. Does it enable 'dark-mode'?
    end
    alt 'dark-mode' was enabled by some variation
        FeatureMgr-->>Context: Return BucketedFeature (status: 'enabled', variables: {...})
    else 'dark-mode' was not enabled
        FeatureMgr-->>Context: Return BucketedFeature (status: 'disabled')
    end
    deactivate FeatureMgr
    Context-->>YourCode: Return BucketedFeature object
    deactivate Context

The key idea is that features are often switched on/off as part of the variations within experiences. FeatureManager figures out which features are enabled by checking the outcome of the visitor's bucketing across relevant experiences.

Implementation Details

The Constructor

Like other managers, the FeatureManager receives and stores references to its dependencies at construction time -- primarily the DataManager and an optional LogManager. No complex setup is needed.

The runFeature Method

This method uses the more general runFeatures method internally:

  1. It first checks if the feature key exists in the project data using the DataManager's getEntity lookup.
  2. The main work is done by calling runFeatures, passing the feature key in a filter object. This tells runFeatures to only care about this specific feature.
  3. It handles the case where the feature is enabled (returning the result from runFeatures) or disabled (returning a status object with disabled).

The runFeatures Method (Core Logic)

This is where the core feature flag evaluation happens:

  1. It gets the relevant experiences from the DataManager.
  2. It loops through each experience and calls the DataManager's getBucketing to see which variation the visitor gets (if any).
  3. Crucially, it inspects the changes array within each bucketed variation.
  4. If a change is of type FULLSTACK_FEATURE, it extracts the feature_id and checks if it matches the desired feature (if filtering).
  5. If it's a match, it builds a BucketedFeature result with status: enabled and extracts any variables.
  6. When no feature filter is provided, it also adds all remaining declared features with a disabled status, giving you the complete picture.
  7. It returns an array containing all the features found to be enabled for this visitor based on their bucketing outcomes.

Conclusion

The FeatureManager provides a focused way to handle feature flags within the Convert SDK. It allows you to easily check if a feature should be enabled for a visitor and retrieve any associated configuration variables.

Key takeaways:

  1. What feature flags are and why FeatureManager is useful.
  2. How to use isFeatureEnabled on the context for a simple on/off check.
  3. How to use runFeature on the context to get the feature's status and variables as a BucketedFeature object.
  4. That FeatureManager works by checking the results of visitor bucketing across relevant experiences (via the DataManager) to see if any variation enables the target feature.

Throughout the last few concepts, we've seen the ExperienceManager and FeatureManager constantly relying on another component to get experiment data, check rules, and perform bucketing. This central component is the DataManager.

Ready to see how the SDK stores and accesses all the project configuration data? Let's dive into the DataManager next!