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.
Installation
Section titled “Installation”npm install @openfeature/server-sdk @featureflip/node @featureflip/openfeature-nodeBoth @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).
Quickstart
Section titled “Quickstart”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.
Context mapping
Section titled “Context mapping”| OpenFeature context | Featureflip context |
|---|---|
targetingKey | user_id (used for rollout bucketing) |
| Any other attribute | Passed 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.
Evaluation reasons
Section titled “Evaluation reasons”| Featureflip outcome | OpenFeature reason | errorCode |
|---|---|---|
| Targeting rule matched | TARGETING_MATCH | — |
| Fallthrough serve | DEFAULT | — |
| Flag disabled in environment | DISABLED | — |
| Prerequisite not met | PREREQUISITE_FAILED | — |
| Flag not found | ERROR | FLAG_NOT_FOUND |
| Wrong value type requested | ERROR | TYPE_MISMATCH |
| Evaluation error | ERROR | GENERAL |
The matched variation key is exposed as variant, and rule/prerequisite
details are available in flagMetadata (ruleId, prerequisiteKey).
Tracking
Section titled “Tracking”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.
Installation
Section titled “Installation”dotnet add package OpenFeaturedotnet add package Featureflip.Clientdotnet add package Featureflip.OpenFeatureQuickstart
Section titled “Quickstart”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 (targetingKey → user_id; Variant, ruleId/prerequisiteKey in
FlagMetadata; PREREQUISITE_FAILED for unmet prerequisites).
.NET limitations
Section titled “.NET limitations”- 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.
Python
Section titled “Python”The featureflip-openfeature-provider package connects the OpenFeature Python
SDK to Featureflip, backed by the featureflip server SDK.
Installation
Section titled “Installation”pip install featureflip-openfeature-providerQuickstart
Section titled “Quickstart”from openfeature import apifrom openfeature.evaluation_context import EvaluationContextfrom 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 types
Section titled “Python types”Python splits OpenFeature’s numeric accessor into get_integer_value and
get_float_value, which Node does not have:
get_integer_valueaccepts integers and whole-number floats (1.0,1e2). JSON does not distinguish1from1.0, so neither can the guard.get_float_valueaccepts any number, including integral ones.- Both reject booleans.
boolis a subclass ofintin Python, so without an explicit exclusion a boolean flag would satisfyget_integer_valueand return1. get_object_valueaccepts objects and arrays but not strings, even though astris aSequencein Python.
Configuration-change events need featureflip >= 2.7.0, the release that added
the SDK’s update hook.
Reacting to flag changes
Section titled “Reacting to flag changes”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 apifrom 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.
Limitations
Section titled “Limitations”getObjectValueaccepts objects and arrays only: a Json flag holding a bare primitive (string/number/boolean) resolves asTYPE_MISMATCHand returns the default.- Node.js, .NET and Python have OpenFeature providers; other SDKs are in progress.