OpenFeature
OpenFeature is a CNCF specification for a vendor-neutral feature flag API. feat ships providers for the OpenFeature web SDK, the OpenFeature Node.js SDK, and the OpenFeature Dart SDK. If you write your code against OpenFeature, you can swap feat for any other OpenFeature-compatible vendor without changing application code.
feat is listed in the OpenFeature ecosystem.
Why OpenFeature
Section titled “Why OpenFeature”Two practical reasons:
- Vendor portability. Your application code calls
OpenFeature.getClient().getBooleanValue(...). Swapping the provider is a one-line change. The rest of your app does not care. - Standard surface. Engineers familiar with OpenFeature do not have to learn a new client API.
Spec reference: openfeature.dev/specification.
Server-side: @feathq/js-sdk
Section titled “Server-side: @feathq/js-sdk”The FeatProvider class is bundled with @feathq/js-sdk. No extra install required.
import { OpenFeature } from "@openfeature/server-sdk";import { FeatClient, FeatProvider } from "@feathq/js-sdk";
const featClient = new FeatClient({ apiKey: process.env.FEAT_SERVER_KEY!, url: "https://data-01.feat.so", // optional; this is the default});
await OpenFeature.setProviderAndWait(new FeatProvider(featClient));
const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue("checkout-v2", false, { targetingKey: "user-123", plan: "pro",});See Node.js SDK for everything else the client supports.
Web: @feathq/openfeature-web
Section titled “Web: @feathq/openfeature-web”The web provider is a separate package because the OpenFeature web spec uses the static-context paradigm: the evaluation context lives on the provider, not on each call. The web provider routes context through OpenFeature.setContext.
npm install @feathq/web-sdk @feathq/openfeature-web @openfeature/web-sdkimport { OpenFeature } from "@openfeature/web-sdk";import { FeatWebClient } from "@feathq/web-sdk";import { FeatWebProvider } from "@feathq/openfeature-web";
const featClient = new FeatWebClient({ apiKey: "feat_cs_…", url: "https://data-01.feat.so", // optional; this is the default});
await OpenFeature.setProviderAndWait(new FeatWebProvider(featClient));await OpenFeature.setContext({ targetingKey: "user-123" });
const client = OpenFeature.getClient();const enabled = client.getBooleanValue("checkout-v2", false); // sync@feathq/web-sdk and @openfeature/web-sdk are peer dependencies. Use OpenFeature.setContext (not the per-call context) for the web provider; it routes through to client.setContext on the underlying feat client.
Live updates and React
Section titled “Live updates and React”The web provider forwards feat’s live flag changes to OpenFeature as ProviderEvents.ConfigurationChanged. That means the OpenFeature React SDK hooks re-render automatically when a flag or the context changes, no manual wiring:
import { OpenFeatureProvider, useBooleanFlagValue } from "@openfeature/react-sdk";
function Checkout() { const enabled = useBooleanFlagValue("checkout-v2", false); return enabled ? <CheckoutV2 /> : <CheckoutV1 />;}
// Wrap your tree once:<OpenFeatureProvider> <Checkout /></OpenFeatureProvider>;For server-rendered React (Next.js App Router, Remix), seed the feat client with a server-evaluated snapshot via bootstrap so the first render is correct. See Server-side rendering.
Dart: feat_openfeature
Section titled “Dart: feat_openfeature”The Dart provider wraps the feat Dart SDK to expose feat through the OpenFeature Dart server SDK’s FeatureProvider interface.
dependencies: feat_sdk: ^0.1.0 feat_openfeature: ^0.1.0 openfeature_dart_server_sdk: ^0.0.22import 'package:feat_openfeature/feat_openfeature.dart';import 'package:feat_sdk/feat_sdk.dart';import 'package:openfeature_dart_server_sdk/open_feature_api.dart';
final featClient = await FeatClient.initialize( FeatClientConfig( apiKey: 'feat_cs_...', // client-side ID or mobile key, never a server key context: EvalContext.user('user-123'), ),);
final api = OpenFeatureAPI();await api.setProviderAndWait(FeatProvider(featClient));
final client = api.getClient('my-app');final enabled = await client.getBooleanFlag('checkout-v2', defaultValue: false);Note: this is a server-paradigm provider (async signatures) wrapping a static-context client, so the per-call context argument is ignored. Change context with FeatProvider.setContext(...) / setEvaluationContext(...), which re-evaluates and refreshes the cache. See Dart SDK.
Type coercion and reasons
Section titled “Type coercion and reasons”The provider returns the supplied default value with an errorCode of TYPE_MISMATCH if you ask a boolean flag for a string, etc. When a flag cannot be evaluated it returns the default with reason: "ERROR" and an errorCode. The web provider maps:
| Situation | errorCode |
|---|---|
| Wrong type requested | TYPE_MISMATCH |
| Client not ready (no context yet) | PROVIDER_NOT_READY |
| Flag does not exist | FLAG_NOT_FOUND |
| Any other evaluation error | GENERAL |
The Dart provider emits FLAG_NOT_FOUND and TYPE_MISMATCH from the same rules; it does not surface PROVIDER_NOT_READY or GENERAL.
For a successful evaluation the provider passes feat’s native reason straight through, unmapped: TARGETING_MATCH, SPLIT, FALLTHROUGH, DEFAULT, DISABLED, or STATIC. See Evaluation order for what each means. (OpenFeature’s reason field is a free-form string, so feat’s vocabulary carries through as-is; errorCode is only set on failures.)
Other languages
Section titled “Other languages”The Go, Python, and Ruby SDKs expose the feat-native API documented in their per-language pages. A provider for each is planned; use the native API in the meantime.