-
Notifications
You must be signed in to change notification settings - Fork 2
FeatureManager
In the previous chapter 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.
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:
- Enable it only for internal testers first.
- Later, enable it for 10% of all visitors.
- Eventually, roll it out to everyone.
- 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.
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
:
-
Knows the Features: It can list all features defined in your Convert project (
getList()
,getFeature()
). -
Checks the Switch: It can tell you if a specific feature (like "dark-mode") should be enabled for a particular visitor (
isFeatureEnabled()
,runFeature()
). -
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). -
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?".
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 our 'user123' should get the "Dark Mode" feature. Assume the feature is identified by the key 'dark-mode'
in your Convert project.
-
Get the Context: First, ensure you have the context for your visitor.
// Assuming 'convert' is your initialized SDK instance const visitorId = 'user123'; const visitorContext = convert.createContext(visitorId, { country: 'Canada' });
-
Check if the Feature is Enabled: Use
context.isFeatureEnabled()
.const featureKey = 'dark-mode'; const isEnabled = visitorContext.isFeatureEnabled(featureKey); if (isEnabled) { console.log(`Feature '${featureKey}' is ENABLED for visitor '${visitorId}'!`); // Now you can apply the dark mode styles } else { console.log(`Feature '${featureKey}' is DISABLED for visitor '${visitorId}'.`); // Use the default light mode }
-
visitorContext.isFeatureEnabled(featureKey)
asks the SDK (specifically, theFeatureManager
via theContext
) whether the feature'dark-mode'
should be active for'user123'
. - It returns a simple boolean:
true
if enabled,false
otherwise.
-
-
Getting Feature Details (including variables): If you need more than just on/off, like configuration values, use
context.runFeature()
.const featureKey = 'dark-mode'; // Attributes might be needed if targeting rules depend on them const attributes = { locationProperties: {}, visitorProperties: { country: 'Canada' } }; const featureResult = visitorContext.runFeature(featureKey, attributes); console.log(featureResult); if (featureResult && featureResult.status === 'enabled') { console.log(`Feature '${featureKey}' is ENABLED.`); console.log('Variables:', featureResult.variables); // Access any variables // Example: Apply dark mode using featureResult.variables.themeColor } else { console.log(`Feature '${featureKey}' is DISABLED.`); }
-
visitorContext.runFeature(featureKey, attributes)
asks the SDK for the full status and data of the feature for this visitor. - The
attributes
are optional but important if your feature targeting rules depend on them (like "only enable for users in Canada"). -
Output: It returns a
BucketedFeature
object.- If enabled, it looks like:
{ key: 'dark-mode', status: 'enabled', variables: { themeColor: '#111', fontSize: '16px' }, id: '...', experienceKey: '...', ... }
- If disabled, it looks like:
{ key: 'dark-mode', status: 'disabled', id: '...' }
- If enabled, it looks like:
- Notice the
status
property ('enabled'
or'disabled'
) and thevariables
object when enabled.
-
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.
-
Call Context Method: Your code calls
visitorContext.runFeature('dark-mode', ...)
. -
Delegate to FeatureManager: The
Context
object passes the request to theFeatureManager
'srunFeature
method. -
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 itsrunFeatures
helper method (which we'll look at below). -
Call
runFeatures
: TherunFeature
method calls the internalrunFeatures
method, potentially filtering to only consider experiences linked to 'dark-mode'. -
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':
dataManager.getBucketing(visitorId, experience.key, attributes)
. This involves the RuleManager and BucketingManager we saw in the previous chapter. -
Examine Variation: If the visitor is bucketed into a variation for an experience,
runFeatures
inspects that variation's definition (specifically, itschanges
data). -
Feature Enabled? It checks if the variation's
changes
explicitly list the 'dark-mode' feature (feature_id
) as being turned on. - 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).
-
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. -
Context Returns: The
Context
returns the finalBucketedFeature
object to your code.
Here's a simplified diagram:
sequenceDiagram
participant YourCode as Your Website 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.
Let's look at simplified snippets.
1. The Constructor (feature-manager.ts
)
Like other managers, it mainly stores references to dependencies.
// File: packages/js-sdk/src/feature-manager.ts (Simplified)
import {DataManagerInterface} from '@convertcom/js-sdk-data';
import {LogManagerInterface} from '@convertcom/js-sdk-logger';
// ... other imports ...
export class FeatureManager implements FeatureManagerInterface {
private _dataManager: DataManagerInterface; // Stores DataManager
private _loggerManager: LogManagerInterface | null;
constructor(
config: Config,
{ dataManager, loggerManager }: { // Receives managers
dataManager: DataManagerInterface;
loggerManager?: LogManagerInterface;
}
) {
// Store the DataManager for later use
this._dataManager = dataManager;
this._loggerManager = loggerManager;
// ... logging ...
}
// ... other methods ...
}
- Very straightforward: it gets the
DataManager
from the main SDK Core and saves it inthis._dataManager
.
2. The runFeature
Method (feature-manager.ts
)
This method uses the more general runFeatures
method internally.
// File: packages/js-sdk/src/feature-manager.ts (Simplified)
// Inside FeatureManager class:
runFeature(
visitorId: string,
featureKey: string,
attributes: BucketingAttributes,
experienceKeys?: Array<string> // Optional: Limit which experiences to check
): BucketedFeature | RuleError | Array<BucketedFeature | RuleError> {
// Check if the feature is even defined in the project data
const declaredFeature = this._dataManager.getEntity(featureKey, 'features');
if (declaredFeature) {
// === Core Logic: Call runFeatures ===
// Ask runFeatures to find enabled features, filtering by our featureKey
const features = this.runFeatures(visitorId, attributes, {
features: [featureKey], // Filter for our specific feature
experiences: experienceKeys // Optional experience filter
});
if (features.length > 0) {
// Found the feature enabled in one or more experiences
return features.length === 1 ? features[0] : features; // Return single or array
}
// If runFeatures didn't find it enabled, return disabled status
return { key: featureKey, status: FeatureStatus.DISABLED, /*...*/ };
} else {
// Feature wasn't even declared in the project data
return { key: featureKey, status: FeatureStatus.DISABLED, /*...*/ };
}
}
- It first checks if the feature key (
featureKey
) exists using_dataManager.getEntity
. - The main work is done by calling
this.runFeatures
, passing thefeatureKey
in thefilter
object. This tellsrunFeatures
to only care about this specific feature. - It handles the case where the feature is enabled (returning the result from
runFeatures
) or disabled.
3. The runFeatures
Method (Simplified Logic)
This is where the core feature flag evaluation happens.
// File: packages/js-sdk/src/feature-manager.ts (Simplified logic)
// Inside FeatureManager class:
runFeatures(
visitorId: string,
attributes: BucketingAttributes,
filter?: Record<string, Array<string>> // Filter by feature/experience keys
): Array<BucketedFeature | RuleError> {
const bucketedFeatures: Array<BucketedFeature> = [];
const experiencesToCheck = /* Get relevant experiences from DataManager based on filter */;
// Loop through experiences the visitor might be part of
for (const experience of experiencesToCheck) {
// Ask DataManager to bucket the visitor for THIS experience
const variationOrError = this._dataManager.getBucketing(
visitorId,
experience.key,
attributes
);
// Skip if visitor didn't qualify (RuleError) or wasn't bucketed
if (/* variationOrError is an error or null */) continue;
const bucketedVariation = variationOrError as BucketedVariation;
// === Check the variation's changes ===
// Loop through changes defined in the variation data
for (const change of bucketedVariation.changes || []) {
// Check if this change enables a feature flag
if (change.type === VariationChangeType.FULLSTACK_FEATURE) {
const featureId = change.data?.feature_id;
const featureData = this._dataManager.getEntityById(featureId, 'features');
// Is this the feature we're looking for (based on filter)?
if (/* featureData matches the filter OR no filter was passed */) {
const variables = /* Extract variables from change.data */;
// Maybe cast variable types (string to number, etc.)
// Found an enabled feature! Add it to our results.
bucketedFeatures.push({
key: featureData.key,
id: featureId,
status: FeatureStatus.ENABLED,
variables: variables,
experienceKey: experience.key // Record which experience enabled it
// ... other details
});
}
}
}
} // End loop through experiences
// If no feature filter was provided, add all OTHER declared features
// with status DISABLED to the list (optional step, not shown in detail)
return bucketedFeatures; // Return the list of found enabled features
}
- It gets the relevant experiences from the DataManager.
- It loops through each experience and calls
_dataManager.getBucketing
to see which variation the visitor gets (if any). - Crucially, it inspects the
changes
array within thebucketedVariation
object. - If a
change
is of typeFULLSTACK_FEATURE
, it extracts thefeature_id
and checks if it matches the desired feature (if filtering). - If it's a match, it builds a
BucketedFeature
object withstatus: 'enabled'
and extracts anyvariables
. - It returns an array containing all the features found to be enabled for this visitor based on their bucketing outcomes.
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.
You've learned:
- What feature flags are and why
FeatureManager
is useful. - How to use
context.isFeatureEnabled()
for a simple on/off check. - How to use
context.runFeature()
to get the feature's status and variables. - 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 chapters, 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 in the next chapter!
Copyrights © 2025 All Rights Reserved by Convert Insights, Inc.
Core Modules
- ConvertSDK / Core
- Context
- ExperienceManager
- FeatureManager
- DataManager
- BucketingManager
- RuleManager
- ApiManager
- EventManager
- Config / Types
Integration Guides