OpenFeature Feature Flags: Node.js and .NET Providers

OpenFeature feature flags in Node.js and .NET: set up the Featureflip provider, map evaluation context correctly, and keep your call sites vendor-neutral.

  • openfeature
  • sdks
  • integrations

A feature flag SDK reaches further into a codebase than almost anything else you install. The call sites end up everywhere. A route handler, a React component, a background worker, a one-off migration script. That is what makes the lock-in question a fair one to ask before you pick a vendor. Moving later means editing several hundred call sites that all name one company’s SDK, most of them in files nobody has opened in a year.

OpenFeature is the answer the ecosystem settled on. It is a CNCF open standard that puts a single vendor-neutral evaluation API in front of whatever flag service you happen to use, with a swappable provider doing the translation behind it. Featureflip ships two: @featureflip/openfeature-node on npm for the OpenFeature Node.js server SDK, and Featureflip.OpenFeature on NuGet for the .NET SDK.

This post covers what the standard actually standardises, how to wire up both providers, the one context detail that quietly breaks percentage rollouts when you get it wrong, and where the standard stops.

Key Takeaways

  • OpenFeature is a CNCF standard for evaluating flags. Your code calls getBooleanValue against an OpenFeature SDK, and a provider behind it talks to one flag service.
  • Featureflip ships an OpenFeature provider for Node.js and .NET today, with the other languages in progress. Setup is one line at startup in both.
  • The provider is the only vendor-specific code in your application. Replacing it changes the backend without touching a single call site.
  • targetingKey maps to Featureflip’s user_id and drives rollout bucketing. Omit it and percentage rollouts serve the control variation to everyone, quietly.
  • The standard covers evaluation only. Creating flags, writing targeting rules, and archiving dead ones stay with each vendor’s own API.
  • Providers are common across established flag vendors, several with wider language coverage than ours. A standard only one vendor shipped would be pointless, so that is the system working.

1. What OpenFeature standardises, and what it leaves alone

Three pieces do the work, and the split between them is what makes the swap possible.

The OpenFeature SDK for your language exposes the standard evaluation API and holds the evaluation context. Behind it sits a provider, a thin adapter that implements the spec against exactly one flag service, and your application picks which one at startup. Every call after that goes through the standard API and never names a vendor.

Where the provider sits between your code and the flag service A left to right chain of four boxes. Application code calls the standard OpenFeature API. The OpenFeature SDK hands the call to a provider. The provider translates it for one flag service, Featureflip, which owns targeting, rollouts and variations. The provider is the only vendor-specific piece and is set once at startup, so replacing it with any other vendor's provider swaps the backend while every call site stays unchanged. One standard API, one swappable provider Your application getBooleanValue(...) in 300 places OpenFeature SDK the standard API holds the context Featureflip provider set once at startup one line of code Featureflip targeting, rollouts, variations, reasons Any other provider same call sites Swap the provider and the backend changes. The 300 call sites on the left never move.
The provider is the seam. Everything vendor-specific lives in one startup line, which is what turns a migration into a configuration change.

Most write-ups stop there. OpenFeature standardises the read path, and only that. Flag creation, targeting rules, environment toggles and archiving all stay with each vendor’s own API, because there is no cross-vendor agreement on what a targeting rule even looks like. Your code becomes portable and your flag inventory does not, a distinction the marketing around the standard tends to blur.

For Featureflip that management side lives in the public REST API, and if you want an agent doing the work there is an MCP server that hands flag management to Claude Code or Cursor. Neither is part of the standard, so both are work you would repeat against a different vendor.

One more thing worth saying plainly. OpenFeature support is common across established flag vendors, and several ship providers for more languages than we do. A standard only one vendor implemented would be worthless, so that is the system working as designed. The portability argument works against us too, and we would rather put it here than let you find it on a vendor comparison page.


2. Node.js, in one startup call

Install the OpenFeature server SDK, the Featureflip SDK, and the provider that joins them:

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

Both @openfeature/server-sdk and @featureflip/node are peer dependencies, so your application controls their versions and installs a single copy of each. That single-copy rule does real work, and section 2 of the OpenFeature integration guide explains why: one copy of @featureflip/node is what lets the provider and any direct SDK usage share the same underlying client core.

Setting the provider is one call:

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' },
);

setProviderAndWait blocks until the flag configuration has loaded, which is what you want at boot. Every read after that is answered from memory inside your process, so the standard API costs you nothing on the request path.

The provider owns the underlying Featureflip client. If you also need the raw SDK, for a track() call outside OpenFeature or anything else the standard does not cover, call FeatureflipClient.get() with the same SDK key and both handles share one client core. You can also build the client yourself and pass the instance to the provider constructor instead of a config object. Either way you end up with a single connection and a single cached config, which is what you want when two parts of the same process both read flags.


3. .NET, the same shape

Three packages, same as Node:

Terminal window
dotnet add package OpenFeature
dotnet add package Featureflip.Client
dotnet add package Featureflip.OpenFeature

And the same one-call setup:

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 takes a server SDK key with optional FeatureFlagOptions, or an existing IFeatureflipClient if you already build one in your DI container. Context mapping and evaluation reasons match the Node provider exactly. That matters for a team running both runtimes, since the mapping only has to be learned once.

Two .NET-specific limits will catch you eventually. The .NET SDK has no custom-event API, so OpenFeature’s Track() quietly does nothing there. It will not throw. Nothing tells you the call went nowhere. And object flags accept objects and arrays only, so a Json flag holding a bare string or number resolves as TYPE_MISMATCH and you get your default back. The .NET SDK reference covers the native surface underneath if you need the parts the standard does not reach.


4. The context detail that breaks rollouts quietly

OpenFeature’s evaluation context has one blessed field, targetingKey, plus any attributes you want to attach. Featureflip maps it like this:

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

Attributes passing through unchanged is the easy half. A targeting rule written against plan or country or org_id in the dashboard keeps working when the value arrives through an OpenFeature context, because the provider does not rename or reshape anything it was not asked to.

The targetingKey half is where the trap lives. That key is what percentage rollouts hash to decide which side of the split a user lands on, and it is what makes that decision stick as you ramp from 10% to 50%. An explicit user_id or userId attribute takes precedence over targetingKey when both are present, which is handy during a migration off the native SDK.

Supply neither and there is nothing to hash. A percentage rollout bucketed by user then serves the control variation to every caller. No exception is thrown and no warning is logged, because a keyless context is a legitimate thing to send for a flag that does not bucket by user. The ramp just sits at zero no matter how high you set the dial. If a rollout looks inert, check the context for a key before you check anything else, and the sticky bucketing entry covers why a stable identifier is load-bearing here.


5. Reasons, variants, and reacting to change

Every evaluation comes back with a reason attached, which is the field you actually reach for at 2am when a flag is the suspect. Featureflip’s outcomes map onto the standard vocabulary:

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 comes back as variant, and flagMetadata carries ruleId and prerequisiteKey so you can trace a decision back to the exact rule or the exact parent flag that gated it. Nothing about flag evaluation becomes a black box because you went through the standard.

Configuration changes surface as OpenFeature events. The initial load fires PROVIDER_READY. Every change after that fires PROVIDER_CONFIGURATION_CHANGED, carrying flagsChanged with the affected keys batched into one 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 through Api.Instance.AddHandler with ProviderEventTypes.ProviderConfigurationChanged.

The definition of a change here is deliberately wide. A flag is reported when it is created, deleted or redefined. It is also reported when a segment its targeting rules reference changes, and when a flag it lists as a prerequisite changes, because both of those alter what it evaluates to even though the flag itself was never edited. A cache keyed on flag identity would miss those two cases and go stale without ever knowing.


6. Where the standard stops

Four boundaries to know before you commit to the standard.

Language coverage is partial. Node.js and .NET have providers today. The other languages are in progress, and until one ships, those services use the native language SDKs, which is a perfectly good place to be. Mixed estates work fine: a Node service can go through OpenFeature while a Go service uses the native SDK against the same flags, because both resolve against one shared evaluation engine.

The standard covers the common surface. Booleans, strings, numbers, objects, context, reasons, events. Anything past that is vendor territory, which is why the escape hatch of reaching for the underlying client matters and why both providers make it easy.

Object flags want objects. Arrays and objects resolve normally. A Json flag holding a bare primitive comes back as TYPE_MISMATCH with your default, in both runtimes.

Management stays vendor-specific. Repeating this from section 1 because it is the boundary people are most surprised by. The standard makes your reads portable. Your flag inventory, rules, and environments still live in one vendor’s system, and moving them is an export and an import.

None of this sits behind a plan. The providers are Apache 2.0 licensed and work on the free Solo tier, so trying Featureflip through the standard costs nothing and commits you to nothing beyond an npm install. If you are still weighing the wider question, the build versus buy analysis puts lock-in next to the other objections teams raise before they commit to a vendor.


The shorter version

OpenFeature is a CNCF standard that puts one vendor-neutral evaluation API in front of any flag service, with a swappable provider behind it. Featureflip ships providers for Node.js (@featureflip/openfeature-node) and .NET (Featureflip.OpenFeature), each set up in a single startup call, after which your code calls getBooleanValue and friends and never names a vendor again. targetingKey maps to Featureflip’s user_id and drives rollout bucketing, so omitting it silently serves the control variation to everyone. Other attributes pass through untouched, so dashboard targeting rules keep working. Reasons, variants and rule metadata all survive the trip, and configuration changes arrive as PROVIDER_CONFIGURATION_CHANGED events that also fire when a referenced segment or prerequisite moves. The standard covers evaluation only, so flag management stays vendor-specific, and language coverage is Node plus .NET today with more in progress.


Frequently asked questions

What is OpenFeature?

OpenFeature is an open specification, hosted by the Cloud Native Computing Foundation, for how applications evaluate feature flags. Your code calls a standard API against an OpenFeature SDK, and a provider plugged in behind it translates those calls for one specific flag service. Because your call sites target the standard rather than a vendor SDK, changing services becomes a configuration change rather than a rewrite. The OpenFeature glossary entry has the short definition.

Does Featureflip have an OpenFeature provider?

Yes, two. @featureflip/openfeature-node connects the OpenFeature Node.js server SDK to Featureflip, and Featureflip.OpenFeature does the same for the OpenFeature .NET SDK. Both are set with a single call at startup and both are available on the free Solo plan. Installation and context mapping for each are in the integration docs.

Which languages have a Featureflip OpenFeature provider?

Node.js and .NET today. Providers for the other server languages are in progress. Every language Featureflip supports has a native SDK regardless, and the two paths resolve flags against the same evaluation engine, so a mixed estate returns identical answers whether a service goes through OpenFeature or the native SDK.

Can I switch feature flag vendors without changing code?

Your evaluation code, largely yes. That is what the provider model is for: the vendor appears in one startup line, and replacing it repoints every call site at once. Your flag configuration is a separate question. OpenFeature does not standardise how flags are created, targeted or archived, so the inventory itself still has to be exported from one system and imported into the next.

Does OpenFeature handle creating and managing flags?

No. The standard covers evaluation only, which means reading a flag’s value for a given context. Creating flags, writing targeting rules, toggling environments and archiving shipped flags all stay with each vendor’s own interface, which for Featureflip means the dashboard, the Management API, or the MCP server.


Featureflip is a focused, flat-priced feature flag platform with sub-millisecond local evaluation and streaming updates, and its OpenFeature providers let you get all of that through the CNCF standard instead of a proprietary SDK. Read the OpenFeature integration guide for the full context and reason mapping, browse the Node SDK reference for what sits underneath, compare plans on the pricing page, and start on the free Solo plan without a credit card.