Running Features

Check feature flag status and retrieve variable values

Features are resolved through variations of relevant experiences. When you run a feature, the SDK finds all experiences whose variations include a fullStackFeature change linked to that feature, evaluates targeting rules, buckets the visitor, and returns the resolved feature status and variable values. See the data model for the full resolution chain.

Run a Single Feature

Returns a single feature's status and variable values for the current visitor.

const feature = userContext.runFeature('feature-key');

// With attributes and experience filter:
const feature = userContext.runFeature('feature-key', {
  locationProperties: { url: '/settings' },
  visitorProperties: { role: 'admin' },
  typeCasting: true,
  experienceKeys: ['specific-experience-key']
});
use ConvertSdk\DTO\BucketedFeature;
use ConvertSdk\Enums\FeatureStatus;
use OpenAPI\Client\BucketingAttributes;

/** @var BucketedFeature|null $feature */
$feature = $context->runFeature('feature-key');

// With attributes and experience filter:
$feature = $context->runFeature('feature-key', new BucketingAttributes([
    'locationProperties' => ['url' => '/settings'],
    'visitorProperties' => ['role' => 'admin'],
    'typeCasting' => true,
    'experienceKeys' => ['specific-experience-key'],
]));
val feature = ctx.runFeature("feature-key")
feature = context.run_feature("feature-key")

# With attributes (visitor properties are flat top-level keys). Note: this Ruby
# surface always resolves across all experiences — the experienceKeys filter and
# typeCasting flag are not part of it.
feature = context.run_feature("feature-key", {
  role: "admin",
  location_properties: { url: "/settings" }
})
// Non-optional by contract: a miss or not-ready visitor yields a disabled Feature, never nil.
let feature = await context.runFeature("feature-key")
if feature.status == .enabled {
    // resolve variables…
}
from convert_sdk import FeatureStatus

# Returns FeatureResult | None — None is a normal miss (feature not declared, or
# the visitor isn't bucketed into a variation carrying it), not an error:
feature = context.run_feature("feature-key")
if feature is not None and feature.status is FeatureStatus.ENABLED:
    headline = feature.variables.get("headline")  # cast to the variable's declared type

Parameters:

ParameterTypeRequiredDescription
featureKeystringYesThe feature's unique key
attributesobjectNoBucketing attributes (see creating a user context for the full attributes reference), plus:
typeCasting (boolean) -- auto-convert values to the variable's defined type (default: true)
experienceKeys (string[]) -- limit evaluation to specific experiences only

Run All Features

Returns all features with their status and variable values for the current visitor.

const features = userContext.runFeatures();

// With attributes:
const features = userContext.runFeatures({
  locationProperties: { url: '/dashboard' },
  visitorProperties: { tier: 'premium' },
  typeCasting: true
});
use ConvertSdk\DTO\BucketedFeature;
use OpenAPI\Client\BucketingAttributes;

/** @var BucketedFeature[] $features */
$features = $context->runFeatures();

// With attributes:
$features = $context->runFeatures(new BucketingAttributes([
    'locationProperties' => ['url' => '/dashboard'],
    'visitorProperties' => ['tier' => 'premium'],
    'typeCasting' => true,
]));
val features = ctx.runFeatures()
features = context.run_features

# With attributes (visitor properties are flat top-level keys):
features = context.run_features({
  tier: "premium",
  location_properties: { url: "/dashboard" }
})
let features = await context.runFeatures()
for feature in features where feature.status == .enabled {
    print("enabled:", feature.key)
}
for feature in context.run_features():
    if feature.status is FeatureStatus.ENABLED:
        print("enabled:", feature.feature_key)

Parameters:

ParameterTypeRequiredDescription
attributesobjectNoBucketing attributes (see creating a user context for the full attributes reference), plus typeCasting (boolean, default: true)

Returns: An array of BucketedFeature objects.

BucketedFeature Fields

Each resolved feature is returned as a BucketedFeature with the following fields:

FieldTypeDescription
featureIdstringThe feature ID
featureKeystringThe feature key
statusstring / FeatureStatus"enabled" or "disabled" (PHP uses the FeatureStatus enum)
variablesobject / arrayThe feature variables with their resolved values

Complete Example

End-to-end flow: initialize the SDK, create a user context, run a feature, and check its status and variables.

import type {
  ConvertInterface, ConvertConfig, ContextInterface, BucketedFeature
} from '@convertcom/js-sdk';
import ConvertSDK from '@convertcom/js-sdk';

const convertSDK = new ConvertSDK({
  sdkKey: 'xxx'
});

convertSDK.onReady().then(() => {
  const context = convertSDK.createContext('user-unique-id');
  const feature = context.runFeature('feature-key');

  if (feature && feature.status === 'enabled') {
    console.log('Feature is enabled with variables:', feature.variables);
  }
});
use ConvertSdk\ConvertSDK;
use ConvertSdk\DTO\BucketedFeature;
use ConvertSdk\Enums\FeatureStatus;

$sdk = ConvertSDK::create([
    'sdkKey' => 'your-sdk-key',
]);

if ($sdk->isReady()) {
    $context = $sdk->createContext('visitor-unique-id');

    /** @var BucketedFeature|null $feature */
    $feature = $context->runFeature('feature-key');

    if ($feature !== null && $feature->status === FeatureStatus::Enabled) {
        echo 'Feature is enabled';
        print_r($feature->variables);
    }
}
import com.convert.sdk.android.ConvertSDK
import com.convert.sdk.android.getString

val sdk = ConvertSDK.builder(applicationContext)
    .sdkKey("YOUR_SDK_KEY")
    .build()

sdk.onReady {
    val context = sdk.createContext("user-unique-id")
    val feature = context.runFeature("feature-key")
    if (feature?.enabled == true) {
        val ctaColor = feature.getString("ctaColor") ?: "#0066ff"
    }
}
require "convert_sdk"

CONVERT_SDK = ConvertSdk.create(sdk_key: "your-sdk-key")

context = CONVERT_SDK.create_context("user-unique-id")
feature = context.run_feature("feature-key")

# run_feature never returns nil — a miss is a DISABLED BucketedFeature:
if feature.status == ConvertSdk::FeatureStatus::ENABLED
  cta_color = feature.variables.fetch("ctaColor", "#0066ff")
end
import ConvertSwiftSDK

let sdk = ConvertSwiftSDK(configuration: ConvertConfiguration(sdkKey: "your-sdk-key"))
try await sdk.ready()

let context = sdk.createContext(visitorId: "user-unique-id")
let feature = await context.runFeature("feature-key")

if feature.status == .enabled {
    // Typed accessor — returns nil on key miss or type mismatch:
    let ctaColor = feature.variable("ctaColor", as: String.self) ?? "#0066ff"
}
from convert_sdk import Core, SDKConfig, FeatureStatus

core = Core(SDKConfig(sdk_key="your-sdk-key")).initialize()

context = core.create_context("user-unique-id")
feature = context.run_feature("feature-key")

# run_feature returns None on a miss (unlike the Ruby/Swift disabled-sentinel):
if feature is not None and feature.status is FeatureStatus.ENABLED:
    cta_color = feature.variables.get("ctaColor", "#0066ff")

core.close()