Skip to content

OpenFeature

OpenFeature is the CNCF open standard for feature flagging. The @featureflip/openfeature-node provider connects the OpenFeature Node.js server SDK to Featureflip, so you can use the vendor-neutral OpenFeature API while Featureflip serves your flags.

Terminal window
npm install @openfeature/server-sdk @featureflip/node @featureflip/openfeature-node

Both @openfeature/server-sdk and @featureflip/node are peer dependencies — your application controls their versions and installs a single copy of each. A single @featureflip/node copy is what lets the provider and any direct SDK usage share one underlying client core (see below).

import { OpenFeature } from '@openfeature/server-sdk';
import { FeatureflipProvider } from '@featureflip/openfeature-node';
await OpenFeature.setProviderAndWait(
new FeatureflipProvider({ sdkKey: 'your-server-sdk-key' }),
);
const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue(
'new-checkout',
false,
{ targetingKey: 'user-42', plan: 'pro' },
);

The provider owns the underlying Featureflip client. If you also need the raw SDK (for track() calls outside OpenFeature, for example), call FeatureflipClient.get() with the same SDK key — both handles share one client core. You can also pass an existing FeatureflipClient instance to the provider constructor instead of a config.

OpenFeature contextFeatureflip context
targetingKeyuser_id (used for rollout bucketing)
Any other attributePassed through unchanged

An explicit user_id (or userId) attribute takes precedence over targetingKey. Without either, percentage rollouts bucketed by user serve the control variation — see the SDK’s keyless-context behavior.

Featureflip outcomeOpenFeature reasonerrorCode
Targeting rule matchedTARGETING_MATCH
Fallthrough serveDEFAULT
Flag disabled in environmentDISABLED
Prerequisite not metPREREQUISITE_FAILED
Flag not foundERRORFLAG_NOT_FOUND
Wrong value type requestedERRORTYPE_MISMATCH
Evaluation errorERRORGENERAL

The matched variation key is exposed as variant, and rule/prerequisite details are available in flagMetadata (ruleId, prerequisiteKey).

OpenFeature tracking calls are forwarded to Featureflip custom events:

client.track('purchase', { targetingKey: 'user-42' }, { value: 9.99 });

The Featureflip.OpenFeature NuGet package connects the OpenFeature .NET SDK to Featureflip, backed by the Featureflip.Client server SDK.

Terminal window
dotnet add package OpenFeature
dotnet add package Featureflip.Client
dotnet add package Featureflip.OpenFeature
using OpenFeature;
using Featureflip.OpenFeature;
await Api.Instance.SetProviderAsync(new FeatureflipProvider("your-server-sdk-key"));
var client = Api.Instance.GetClient();
var enabled = await client.GetBooleanValueAsync("new-checkout", false,
EvaluationContext.Builder().SetTargetingKey("user-42").Build());

The constructor accepts a server SDK key (with optional FeatureFlagOptions) or an existing IFeatureflipClient. Context mapping and reasons match the Node provider (targetingKeyuser_id; Variant, ruleId/prerequisiteKey in FlagMetadata; PREREQUISITE_FAILED for unmet prerequisites).

  • The .NET SDK has no custom-event API, so OpenFeature Track() is a no-op.
  • Object flags accept objects and arrays only; a bare primitive resolves as TYPE_MISMATCH.

The featureflip-openfeature-provider package connects the OpenFeature Python SDK to Featureflip, backed by the featureflip server SDK.

Terminal window
pip install featureflip-openfeature-provider
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from featureflip_openfeature import FeatureflipProvider
api.set_provider_and_wait(FeatureflipProvider(sdk_key="your-server-sdk-key"))
client = api.get_client()
enabled = client.get_boolean_value(
"new-checkout",
False,
EvaluationContext(targeting_key="user-42", attributes={"plan": "pro"}),
)

The constructor takes either an SDK key (with optional Config) or an existing FeatureflipClient via the client= keyword. Context mapping and reasons match the Node provider. targeting_key becomes user_id, the matched variation is exposed as variant, ruleId and prerequisiteKey are carried in flag_metadata, and an unmet prerequisite resolves as PREREQUISITE_FAILED.

Ownership follows the .NET provider rather than Node: the provider closes the client only when it created one. A client you pass in is a refcounted handle you still hold, so closing it would make your own handle start returning defaults.

FeatureflipClient(...) blocks on the initial flag fetch. With the sdk_key form the provider defers construction to initialize(), so that wait happens inside OpenFeature’s provider setup rather than in your constructor.

Reach for set_provider_and_wait rather than set_provider. The plain form runs initialize() on a background thread and returns straight away, so flags may not have loaded when the next line evaluates one and you get your default back. This is the Python equivalent of awaiting setProviderAndWait in Node.

Python splits OpenFeature’s numeric accessor into get_integer_value and get_float_value, which Node does not have:

  • get_integer_value accepts integers and whole-number floats (1.0, 1e2). JSON does not distinguish 1 from 1.0, so neither can the guard.
  • get_float_value accepts any number, including integral ones.
  • Both reject booleans. bool is a subclass of int in Python, so without an explicit exclusion a boolean flag would satisfy get_integer_value and return 1.
  • get_object_value accepts objects and arrays but not strings, even though a str is a Sequence in Python.

Configuration-change events need featureflip >= 2.7.0, the release that added the SDK’s update hook.

The Node provider emits OpenFeature’s PROVIDER_CONFIGURATION_CHANGED whenever flag configuration changes after startup. The event carries flagsChanged — the keys of the flags affected by that change, batched into a single event:

import { OpenFeature, ProviderEvents } from "@openfeature/server-sdk";
OpenFeature.addHandler(ProviderEvents.ConfigurationChanged, (details) => {
console.log("flags changed:", details?.flagsChanged);
});

The .NET provider emits the same event, from version 0.2.0:

using OpenFeature;
using OpenFeature.Constant;
Api.Instance.AddHandler(ProviderEventTypes.ProviderConfigurationChanged, details =>
{
Console.WriteLine($"flags changed: {string.Join(", ", details.FlagsChanged ?? [])}");
});

The Python provider emits it too:

from openfeature import api
from openfeature.event import ProviderEvent
api.add_handler(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED,
lambda details: print("flags changed:", details.flags_changed),
)

A flag is reported when it is created, deleted, redefined, or when a segment its targeting rules reference changes. A flag is also reported when a flag it lists as a prerequisite changes, since that alters what it evaluates to. The initial flag load does not fire the event — that is signalled by PROVIDER_READY.

  • getObjectValue accepts objects and arrays only: a Json flag holding a bare primitive (string/number/boolean) resolves as TYPE_MISMATCH and returns the default.
  • Node.js, .NET and Python have OpenFeature providers; other SDKs are in progress.