# OpenFeature > OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging. ## Documentation ### tutorials #### five-minutes-to-feature-flags We’re going to add feature flagging to a Node service in under five minutes using OpenFeature, the open, vendor-agnostic feature flagging SDK. - [Five Minutes to Feature Flags](https://openfeature.dev/docs/tutorials/five-minutes-to-feature-flags.md): We’re going to add feature flagging to a Node service in under five minutes using OpenFeature, the open, vendor-agnostic feature flagging SDK. #### getting-started - [Getting Started with the OpenFeature .NET SDK](https://openfeature.dev/docs/tutorials/getting-started/dotnet.md): Getting Started with the OpenFeature .NET SDK - [Getting Started with the OpenFeature Go SDK and Go Gin](https://openfeature.dev/docs/tutorials/getting-started/go.md): Getting Started with the OpenFeature Go SDK and Go Gin - [Getting Started with the OpenFeature Java SDK and Dropwizard](https://openfeature.dev/docs/tutorials/getting-started/java/dropwizard.md): Getting Started with the OpenFeature Java SDK and Dropwizard - [Getting Started with the OpenFeature Java SDK and Spring Boot](https://openfeature.dev/docs/tutorials/getting-started/java/spring-boot.md): Getting Started with the OpenFeature Java SDK and Spring Boot - [Getting Started with the OpenFeature JavaScript SDK on Express](https://openfeature.dev/docs/tutorials/getting-started/node.md): Getting Started with the OpenFeature JavaScript SDK on Express - [Getting Started with the OpenFeature PHP SDK on Laravel](https://openfeature.dev/docs/tutorials/getting-started/php.md): Getting Started with the OpenFeature PHP SDK on Laravel - [Getting Started with the OpenFeature Python SDK](https://openfeature.dev/docs/tutorials/getting-started/python.md): Getting Started with the OpenFeature Python SDK on Flask - [Getting Started with the OpenFeature Rust SDK and Axum](https://openfeature.dev/docs/tutorials/getting-started/rust.md): Getting Started with the OpenFeature Rust SDK and Axum (with Docker example) #### open-feature-cli ⚠️ The OpenFeature CLI is experimental! - [OpenFeature CLI](https://openfeature.dev/docs/tutorials/open-feature-cli.md): ⚠️ The OpenFeature CLI is experimental! #### open-feature-operator - [In-Process and Client-side Evaluation with flagd and OpenFeature Operator](https://openfeature.dev/docs/tutorials/open-feature-operator/advanced-topics.md): This tutorial outlines some of the advanced features and configurations supported by the OpenFeature operator and flagd. - [Quick Start: Cloud Native Feature-Flagging with the OpenFeature Operator](https://openfeature.dev/docs/tutorials/open-feature-operator/quick-start.md): In the following tutorial, we'll see how to leverage flagd and the OpenFeature Operator to enable cloud-native, self-hosted feature flags in your Kubernetes cluster. flagd is a "feature flag daemon with a Unix philosophy". ### reference #### intro OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. - [Welcome to OpenFeature](https://openfeature.dev/docs/reference/intro.md): OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. #### concepts - [Evaluation API](https://openfeature.dev/docs/reference/concepts/evaluation-api.md): The Evaluation API is the primary component of OpenFeature that application authors interact with. - [Evaluation Context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md): The evaluation context is a container for arbitrary contextual data that can be used as a basis for dynamic evaluation. - [Events](https://openfeature.dev/docs/reference/concepts/events.md): Events enable the ability to react to state changes in the provider or underlying flag management system. - [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md): Hooks are a mechanism that allow for the addition of arbitrary behavior at well-defined points of the flag evaluation life-cycle. - [Providers](https://openfeature.dev/docs/reference/concepts/provider.md): Providers are responsible for performing flag evaluations. - [SDK Paradigms](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md): OpenFeature SDKs come in two "flavors"; those intended for use in server applications and those intended for use in client applications. - [Tracking](https://openfeature.dev/docs/reference/concepts/tracking.md): Tracking in OpenFeature is the process of associating metrics or KPIs with feature flag evaluation contexts. #### sdks OpenFeature SDKs and components for a range of supported languages and runtimes. - [SDK Technologies](https://openfeature.dev/docs/reference/sdks.md): OpenFeature SDKs and components for a range of supported languages and runtimes. - [Client](https://openfeature.dev/docs/reference/sdks/client.md): OpenFeature SDKs and components for client-side usage. - [OpenFeature Dart Client SDK](https://openfeature.dev/docs/reference/sdks/client/dart.md): Install and use the OpenFeature Dart SDK - [OpenFeature Kotlin SDK](https://openfeature.dev/docs/reference/sdks/client/kotlin.md): Install and use the OpenFeature Kotlin SDK - [OpenFeature iOS SDK](https://openfeature.dev/docs/reference/sdks/client/swift.md): Install and use the OpenFeature iOS SDK - [OpenFeature Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md): Install and use the OpenFeature Web SDK - [OpenFeature Angular SDK](https://openfeature.dev/docs/reference/sdks/client/web/angular.md): Install and use the OpenFeature Angular SDK - [OpenFeature React SDK](https://openfeature.dev/docs/reference/sdks/client/web/react.md): Install and use the OpenFeature React SDK - [SDK Compatibility Overview](https://openfeature.dev/docs/reference/sdks/sdk-compatibility.md): An overview of the status and supported features of the OpenFeature SDKs - [Server](https://openfeature.dev/docs/reference/sdks/server.md): OpenFeature SDKs and components for server-side usage. - [OpenFeature C++ SDK](https://openfeature.dev/docs/reference/sdks/server/cpp.md): Install and use the OpenFeature C++ SDK - [OpenFeature Dart SDK](https://openfeature.dev/docs/reference/sdks/server/dart.md): Install and use the OpenFeature Dart SDK - [OpenFeature .NET SDK](https://openfeature.dev/docs/reference/sdks/server/dotnet.md): Install and use the OpenFeature .NET SDK - [OpenFeature Go SDK](https://openfeature.dev/docs/reference/sdks/server/go.md): Install and use the OpenFeature Go SDK - [OpenFeature Java SDK](https://openfeature.dev/docs/reference/sdks/server/java.md): Install and use the OpenFeature Java SDK - [OpenFeature Node.js SDK](https://openfeature.dev/docs/reference/sdks/server/javascript.md): Install and use the OpenFeature Node.js SDK - [OpenFeature NestJS SDK](https://openfeature.dev/docs/reference/sdks/server/javascript/nestjs.md): Install and use the OpenFeature NestJS SDK - [OpenFeature PHP SDK](https://openfeature.dev/docs/reference/sdks/server/php.md): Install and use the OpenFeature PHP SDK - [OpenFeature Python SDK](https://openfeature.dev/docs/reference/sdks/server/python.md): Install and use the OpenFeature Python SDK - [OpenFeature Ruby SDK](https://openfeature.dev/docs/reference/sdks/server/ruby.md): Install and use the OpenFeature Ruby SDK - [OpenFeature Rust SDK](https://openfeature.dev/docs/reference/sdks/server/rust.md): Install and use the OpenFeature Rust SDK #### other-technologies - [OpenFeature CLI](https://openfeature.dev/docs/reference/other-technologies/cli.md): The OpenFeature CLI is a command-line tool designed to improve the developer experience when working with feature flags. - [OpenFeature MCP Server](https://openfeature.dev/docs/reference/other-technologies/mcp.md): The OpenFeature Model Context Protocol (MCP) Server enables AI coding assistants to interact with OpenFeature through a standardized protocol. - [OpenFeature Remote Evaluation Protocol (OFREP)](https://openfeature.dev/docs/reference/other-technologies/ofrep.md): The OpenFeature Remote Evaluation Protocol (OFREP) is an API specification for feature flagging that enables vendor-agnostic communication between applications and flag management systems. It defines a standard API layer between the provider and the flag management system, allowing any open source or commercial system to implement the protocol and be compatible with community-maintained providers. - [OpenFeature Remote Evaluation Protocol (OFREP) (0.4.0)](https://openfeature.dev/docs/reference/other-technologies/ofrep/openapi.md) ## Specification - [OpenFeature Specification](https://openfeature.dev/specification.md): An introduction to the OpenFeature specification. - [Appendix A: Included Utilities](https://openfeature.dev/specification/appendix-a.md): Information on OpenFeature ecosystem utilities - [Appendix B: Gherkin Suites](https://openfeature.dev/specification/appendix-b.md): A Set of End-to-End Tests for Validating OpenFeature Implementations - [Appendix C: OpenFeature Remote Evaluation Protocol](https://openfeature.dev/specification/appendix-c.md): A unified protocol for feature flagging - [Appendix D: Observability](https://openfeature.dev/specification/appendix-d.md): Conventions for OpenFeature telemetry signals - [Appendix E: Migrations](https://openfeature.dev/specification/appendix-e.md): Migration guidance for breaking spec changes - [Glossary](https://openfeature.dev/specification/glossary.md): A list of terms used within the OpenFeature specification. - [3. Evaluation Context](https://openfeature.dev/specification/sections/evaluation-context.md): The specification that defines the structure and expectations of evaluation context. - [5. Events](https://openfeature.dev/specification/sections/events.md): Specification defining event semantics - [1. Flag Evaluation API](https://openfeature.dev/specification/sections/flag-evaluation.md): The specification that defines the developer facing feature flag evaluation API. - [4. Hooks](https://openfeature.dev/specification/sections/hooks.md): The specification that defines the expectations and life cycle of hooks. - [2. Provider](https://openfeature.dev/specification/sections/providers.md): The specification that defines the interfaces, behaviors and responsibilities of providers. - [6. Tracking](https://openfeature.dev/specification/sections/tracking.md): Specification defining tracking API - [Types and Data Structures](https://openfeature.dev/specification/types.md): A description of types and data structures used within the OpenFeature specification. ## Ecosystem - [OpenFeature Ecosystem](https://openfeature.dev/ecosystem.md): Discover OpenFeature SDKs, providers, hooks, OFREP APIs, and integrations. ## Optional - [flagd](https://flagd.dev/): OpenFeature-compliant feature flag evaluation engine - [OpenFeature on GitHub](https://github.com/open-feature) --- # Full Documentation Content # OpenFeature Ecosystem SDKs, providers, hooks, OFREP APIs, and integrations that work with OpenFeature. ## SDKs * [Java SDK](https://openfeature.dev/docs/reference/sdks/server/java.md) (Java, Server) * [Node.js SDK](https://openfeature.dev/docs/reference/sdks/server/javascript.md) (JavaScript, Server) * [NestJS SDK](https://openfeature.dev/docs/reference/sdks/server/javascript/nestjs.md) (NestJS, Server) * [.NET SDK](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) (.NET, Server) * [Go SDK](https://openfeature.dev/docs/reference/sdks/server/go.md) (Go, Server) * [Python SDK](https://openfeature.dev/docs/reference/sdks/server/python.md) (Python, Server) * [PHP SDK](https://openfeature.dev/docs/reference/sdks/server/php.md) (PHP, Server) * [Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md) (JavaScript, Client) * [React SDK](https://openfeature.dev/docs/reference/sdks/client/web/react.md) (React, Client) * [Kotlin SDK](https://openfeature.dev/docs/reference/sdks/client/kotlin.md) (Kotlin, Client) * [iOS SDK](https://openfeature.dev/docs/reference/sdks/client/swift.md) (Swift, Client) * [Ruby SDK](https://openfeature.dev/docs/reference/sdks/server/ruby.md) (Ruby, Server) * [Angular SDK](https://openfeature.dev/docs/reference/sdks/client/web/angular.md) (Angular, Client) * [Dart SDK](https://openfeature.dev/docs/reference/sdks/server/dart.md) (Dart, Server) * [Dart SDK](https://openfeature.dev/docs/reference/sdks/client/dart.md) (Dart, Client) * [Rust SDK](https://openfeature.dev/docs/reference/sdks/server/rust.md) (Rust, Server) * [C++ SDK](https://openfeature.dev/docs/reference/sdks/server/cpp.md) (C++, Server) * [Flags SDK](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) (Next.js, Client) * [Flags SDK](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) (SvelteKit, Client) ## Providers ### AB Tasty * [AB Tasty JavaScript Node.js Provider](https://github.com/flagship-io/openfeature-provider-js) (JavaScript, Server, vendor supported) * [AB Tasty JavaScript Web Provider](https://github.com/flagship-io/openfeature-provider-js-client) (JavaScript, Client, vendor supported) * [AB Tasty Swift Client Provider](https://github.com/flagship-io/openfeature-provider-iOS) (Swift, Client, vendor supported) ### Ageris * [Ageris Java Server Provider](https://github.com/lichtsinn/ageris#using-ageris-through-openfeature) (Java, Server, vendor supported) ### AWS SSM * [AWS SSM JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/aws-ssm) (JavaScript, Server) * [AWS SSM Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/aws-ssm) (Go, Server) ### Azure App Configuration * [Azure App Configuration JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/azure-app-configuration) (JavaScript, Server) ### Bucketeer * [Bucketeer Swift Client Provider](https://github.com/bucketeer-io/openfeature-swift-client-sdk) (Swift, Client, vendor supported) * [Bucketeer Kotlin Client Provider](https://github.com/bucketeer-io/openfeature-kotlin-client-sdk) (Kotlin, Client, vendor supported) * [Bucketeer JavaScript Web Provider](https://www.npmjs.com/package/@bucketeer/openfeature-js-client-sdk) (JavaScript, Client, vendor supported) * [Bucketeer React Web Provider](https://github.com/bucketeer-io/openfeature-js-client-sdk/blob/main/docs/react.md) (React, Client, vendor supported) * [Bucketeer JavaScript Node.js Provider](https://www.npmjs.com/package/@bucketeer/openfeature-node-server-sdk) (JavaScript, Server, vendor supported) * [Bucketeer Go Server Provider](https://github.com/bucketeer-io/openfeature-go-server-sdk) (Go, Server, vendor supported) ### CloudBees * [CloudBees JavaScript Node.js Provider](https://github.com/cloudbees-oss/cloudbees-openfeature-provider-node) (JavaScript, Server, vendor supported) * [CloudBees JavaScript Web Provider](https://github.com/cloudbees-oss/cloudbees-openfeature-provider-browser) (JavaScript, Client, vendor supported) * [CloudBees Go Server Provider](https://github.com/cloudbees-oss/cloudbees-openfeature-provider-go) (Go, Server, vendor supported) * [CloudBees Java Server Provider](https://github.com/cloudbees-oss/cloudbees-openfeature-provider-java) (Java, Server, vendor supported) * [CloudBees .NET Server Provider](https://github.com/cloudbees-oss/cloudbees-openfeature-provider-dotnet) (.NET, Server, vendor supported) * [CloudBees PHP Server Provider](https://github.com/open-feature/php-sdk-contrib/tree/main/providers/CloudBees) (PHP, Server) * [CloudBees Python Server Provider](https://github.com/cloudbees-oss/cloudbees-openfeature-provider-python) (Python, Server, vendor supported) ### Cloudflare Flagship * [Cloudflare Flagship JavaScript Web Provider](https://developers.cloudflare.com/flagship/sdk/client-provider/) (JavaScript, Client, vendor supported) * [Cloudflare Flagship JavaScript Node.js Provider](https://developers.cloudflare.com/flagship/sdk/server-provider/) (JavaScript, Server, vendor supported) * [Cloudflare Flagship Go Server Provider](https://developers.cloudflare.com/flagship/sdk/go/) (Go, Server, vendor supported) * [Cloudflare Flagship Python Server Provider](https://developers.cloudflare.com/flagship/sdk/python/) (Python, Server, vendor supported) ### Confidence by Spotify * [Confidence by Spotify JavaScript Node.js Provider](https://github.com/spotify/confidence-sdk-js) (JavaScript, Server, vendor supported) * [Confidence by Spotify JavaScript Web Provider](https://github.com/spotify/confidence-sdk-js) (JavaScript, Client, vendor supported) * [Confidence by Spotify Java Server Provider](https://github.com/spotify/confidence-sdk-java) (Java, Server, vendor supported) * [Confidence by Spotify Kotlin Client Provider](https://github.com/spotify/confidence-sdk-android) (Kotlin, Client, vendor supported) * [Confidence by Spotify Swift Client Provider](https://github.com/spotify/confidence-sdk-swift) (Swift, Client, vendor supported) * [Confidence by Spotify Python Server Provider](https://github.com/spotify/confidence-sdk-python) (Python, Server, vendor supported) * [Confidence by Spotify Go Server Provider](https://github.com/spotify/confidence-sdk-go) (Go, Server, vendor supported) ### ConfigBee * [ConfigBee JavaScript Web Provider](https://github.com/configbee/cb-openfeature-provider-web) (JavaScript, Client, vendor supported) ### ConfigCat * [ConfigCat JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/config-cat) (JavaScript, Server, vendor supported) * [ConfigCat NestJS Node.js Provider](https://configcat.com/docs/sdk-reference/openfeature/nestjs/) (NestJS, Server, vendor supported) * [ConfigCat JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/config-cat-web) (JavaScript, Client, vendor supported) * [ConfigCat Angular Web Provider](https://configcat.com/docs/sdk-reference/openfeature/angular/) (Angular, Client, vendor supported) * [ConfigCat React Web Provider](https://configcat.com/docs/sdk-reference/openfeature/react/) (React, Client, vendor supported) * [ConfigCat Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/configcat) (Go, Server, vendor supported) * [ConfigCat Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/configcat) (Java, Server, vendor supported) * [ConfigCat .NET Server Provider](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.ConfigCat) (.NET, Server, vendor supported) * [ConfigCat PHP Server Provider](https://github.com/configcat/openfeature-php) (PHP, Server, vendor supported) * [ConfigCat Python Server Provider](https://github.com/configcat/openfeature-python) (Python, Server, vendor supported) * [ConfigCat Rust Server Provider](https://github.com/configcat/openfeature-rust) (Rust, Server, vendor supported) * [ConfigCat Ruby Server Provider](https://github.com/configcat/openfeature-ruby) (Ruby, Server, vendor supported) * [ConfigCat Kotlin Client Provider](https://github.com/configcat/openfeature-kotlin) (Kotlin, Client, vendor supported) * [ConfigCat Swift Client Provider](https://github.com/configcat/openfeature-swift) (Swift, Client, vendor supported) ### ConfigDirector * [ConfigDirector JavaScript Node.js Provider](https://docs.configdirector.com/sdks/server/openfeature-node) (JavaScript, Server, vendor supported) * [ConfigDirector Java Server Provider](https://docs.configdirector.com/sdks/openfeature/java) (Java, Server, vendor supported) * [ConfigDirector .NET Server Provider](https://docs.configdirector.com/sdks/openfeature/dotnet) (.NET, Server, vendor supported) * [ConfigDirector Python Server Provider](https://docs.configdirector.com/sdks/openfeature/python) (Python, Server, vendor supported) * [ConfigDirector Dart Client Provider](https://docs.configdirector.com/sdks/openfeature/flutter) (Dart, Client, vendor supported) * [ConfigDirector JavaScript Web Provider](https://docs.configdirector.com/sdks/browser/openfeature-web) (JavaScript, Client, vendor supported) * [ConfigDirector Kotlin Client Provider](https://docs.configdirector.com/sdks/openfeature/android) (Kotlin, Client, vendor supported) * [ConfigDirector Swift Client Provider](https://docs.configdirector.com/sdks/openfeature/swift) (Swift, Client, vendor supported) ### Datadog * [Datadog JavaScript Node.js Provider](https://docs.datadoghq.com/feature_flags/server/nodejs/) (JavaScript, Server, vendor supported) * [Datadog JavaScript Web Provider](https://docs.datadoghq.com/feature_flags/client/javascript/) (JavaScript, Client, vendor supported) * [Datadog React Client Provider](https://docs.datadoghq.com/feature_flags/client/react/) (React, Client, vendor supported) * [Datadog Swift Client Provider](https://docs.datadoghq.com/feature_flags/client/ios/) (Swift, Client, vendor supported) * [Datadog Kotlin Client Provider](https://docs.datadoghq.com/feature_flags/client/android/) (Kotlin, Client, vendor supported) * [Datadog Go Server Provider](https://docs.datadoghq.com/feature_flags/server/go/) (Go, Server, vendor supported) * [Datadog .NET Server Provider](https://docs.datadoghq.com/feature_flags/server/dotnet/) (.NET, Server, vendor supported) * [Datadog Java Server Provider](https://docs.datadoghq.com/feature_flags/server/java/) (Java, Server, vendor supported) * [Datadog Python Server Provider](https://docs.datadoghq.com/feature_flags/server/python/) (Python, Server, vendor supported) * [Datadog Ruby Server Provider](https://docs.datadoghq.com/feature_flags/server/ruby/) (Ruby, Server, vendor supported) * [Datadog PHP Server Provider](https://docs.datadoghq.com/feature_flags/server/php/) (PHP, Server, vendor supported) ### DevCycle * [DevCycle JavaScript Node.js Provider](https://docs.devcycle.com/sdk/server-side-sdks/node/node-openfeature/) (JavaScript, Server, vendor supported) * [DevCycle NestJS Node.js Provider](https://docs.devcycle.com/sdk/server-side-sdks/nestjs/nestjs-openfeature/) (NestJS, Server, vendor supported) * [DevCycle Java Server Provider](https://docs.devcycle.com/sdk/server-side-sdks/java/java-openfeature/) (Java, Server, vendor supported) * [DevCycle Go Server Provider](https://docs.devcycle.com/sdk/server-side-sdks/go/go-openfeature/) (Go, Server, vendor supported) * [DevCycle .NET Server Provider](https://docs.devcycle.com/sdk/server-side-sdks/dotnet/dotnet-openfeature/) (.NET, Server, vendor supported) * [DevCycle JavaScript Web Provider](https://docs.devcycle.com/sdk/client-side-sdks/javascript/javascript-openfeature/) (JavaScript, Client, vendor supported) * [DevCycle React Web Provider](https://docs.devcycle.com/sdk/client-side-sdks/react/react-openfeature/) (React, Client, vendor supported) * [DevCycle Angular Web Provider](https://docs.devcycle.com/sdk/client-side-sdks/angular/angular-install/) (Angular, Client, vendor supported) * [DevCycle PHP Server Provider](https://docs.devcycle.com/sdk/server-side-sdks/php/php-openfeature/) (PHP, Server, vendor supported) * [DevCycle Python Server Provider](https://docs.devcycle.com/sdk/server-side-sdks/python/python-openfeature/) (Python, Server, vendor supported) * [DevCycle Ruby Server Provider](https://docs.devcycle.com/sdk/server-side-sdks/ruby/ruby-openfeature/) (Ruby, Server, vendor supported) * [DevCycle Swift Client Provider](https://docs.devcycle.com/sdk/client-side-sdks/ios/ios-openfeature/) (Swift, Client, vendor supported) * [DevCycle Kotlin Client Provider](https://docs.devcycle.com/sdk/client-side-sdks/android/android-openfeature/) (Kotlin, Client, vendor supported) ### dif.sh * [dif.sh JavaScript Node.js Provider](https://www.npmjs.com/package/@dif.sh/openfeature) (JavaScript, Server, vendor supported) ### Environment Variable * [Environment Variable Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/from-env) (Go, Server, vendor supported) * [Environment Variable Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/env-var) (Java, Server, vendor supported) * [Environment Variable JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/env-var) (JavaScript, Server, vendor supported) * [Environment Variable Rust Server Provider](https://github.com/open-feature/rust-sdk-contrib/tree/main/crates/env-var) (Rust, Server, vendor supported) ### Feat * [Feat JavaScript Web Provider](https://github.com/feathq/openfeature-web) (JavaScript, Client, vendor supported) * [Feat JavaScript Node.js Provider](https://github.com/feathq/js-sdk) (JavaScript, Server, vendor supported) * [Feat Dart Server Provider](https://github.com/feathq/openfeature-dart) (Dart, Server, vendor supported) ### FeatBit * [FeatBit Java Server Provider](https://github.com/featbit/featbit-openfeature-provider-java-server) (Java, Server, vendor supported) * [FeatBit .NET Server Provider](https://github.com/featbit/openfeature-provider-dotnet-server) (.NET, Server, vendor supported) * [FeatBit JavaScript Node.js Provider](https://github.com/featbit/openfeature-provider-node-server) (JavaScript, Server, vendor supported) * [FeatBit JavaScript Web Provider](https://github.com/featbit/openfeature-provider-js-client) (JavaScript, Client, vendor supported) ### Featureflip * [Featureflip JavaScript Node.js Provider](https://featureflip.io/docs/integrations/openfeature/) (JavaScript, Server, vendor supported) * [Featureflip .NET Server Provider](https://featureflip.io/docs/integrations/openfeature/) (.NET, Server, vendor supported) * [Featureflip Python Server Provider](https://featureflip.io/docs/integrations/openfeature/) (Python, Server, vendor supported) * [Featureflip Go Server Provider](https://featureflip.io/docs/integrations/openfeature/) (Go, Server, vendor supported) * [Featureflip Java Server Provider](https://featureflip.io/docs/integrations/openfeature/) (Java, Server, vendor supported) * [Featureflip PHP Server Provider](https://featureflip.io/docs/integrations/openfeature/) (PHP, Server, vendor supported) * [Featureflip Ruby Server Provider](https://featureflip.io/docs/integrations/openfeature/) (Ruby, Server, vendor supported) ### Featurevisor * [Featurevisor JavaScript Web Provider](https://featurevisor.com/docs/sdks/browser/#open-feature) (JavaScript, Client, vendor supported) * [Featurevisor JavaScript Node.js Provider](https://featurevisor.com/docs/sdks/nodejs/#open-feature) (JavaScript, Server, vendor supported) * [Featurevisor Go Server Provider](https://featurevisor.com/docs/sdks/go/#open-feature) (Go, Server, vendor supported) * [Featurevisor Swift Client Provider](https://featurevisor.com/docs/sdks/swift/#open-feature) (Swift, Client, vendor supported) * [Featurevisor Java Server Provider](https://featurevisor.com/docs/sdks/java/#open-feature) (Java, Server, vendor supported) * [Featurevisor Ruby Server Provider](https://featurevisor.com/docs/sdks/ruby/#open-feature) (Ruby, Server, vendor supported) * [Featurevisor Python Server Provider](https://featurevisor.com/docs/sdks/python/#open-feature) (Python, Server, vendor supported) * [Featurevisor PHP Server Provider](https://featurevisor.com/docs/sdks/php/#open-feature) (PHP, Server, vendor supported) ### Firebase Remote Config * [Firebase Remote Config Swift Client Provider](https://github.com/fumito-ito/FirebaseRemoteConfig-OpenFeature-Provider-Swift) (Swift, Client) ### flagd * [flagd JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flagd) (JavaScript, Server, vendor supported) * [flagd JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flagd-web) (JavaScript, Client, vendor supported) * [flagd Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flagd) (Go, Server, vendor supported) * [flagd Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/flagd) (Java, Server, vendor supported) * [flagd .NET Server Provider](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Providers.Flagd) (.NET, Server, vendor supported) * [flagd PHP Server Provider](https://github.com/open-feature/php-sdk-contrib/tree/main/providers/Flagd) (PHP, Server, vendor supported) * [flagd Python Server Provider](https://github.com/open-feature/python-sdk-contrib/tree/main/providers/openfeature-provider-flagd) (Python, Server, vendor supported) * [flagd Rust Server Provider](https://github.com/open-feature/rust-sdk-contrib/tree/main/crates/flagd) (Rust, Server, vendor supported) ### Flagsmith * [Flagsmith Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flagsmith) (Go, Server, vendor supported) * [Flagsmith Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/flagsmith) (Java, Server, vendor supported) * [Flagsmith JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flagsmith-client) (JavaScript, Client, vendor supported) * [Flagsmith JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flagsmith) (JavaScript, Server, vendor supported) * [Flagsmith Kotlin Client Provider](https://github.com/Flagsmith/flagsmith-openfeature-provider-kotlin) (Kotlin, Client, vendor supported) * [Flagsmith .NET Server Provider](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.Flagsmith) (.NET, Server, vendor supported) * [Flagsmith Python Server Provider](https://github.com/Flagsmith/flagsmith-openfeature-provider-python) (Python, Server, vendor supported) * [Flagsmith Ruby Server Provider](https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-flagsmith-provider) (Ruby, Server, vendor supported) * [Flagsmith Rust Server Provider](https://github.com/open-feature/rust-sdk-contrib/tree/main/crates/flagsmith) (Rust, Server, vendor supported) * [Flagsmith Swift Client Provider](https://github.com/Flagsmith/flagsmith-openfeature-swift-provider) (Swift, Client, vendor supported) ### flagtide * [flagtide JavaScript Web Provider](https://github.com/adiyy2001/flagtide/tree/main/libs/openfeature-web#readme) (JavaScript, Client, vendor supported) ### Flagward * [Flagward JavaScript Web Provider](https://docs.flagward.com/sdks/openfeature) (JavaScript, Client, vendor supported) ### Flipswitch * [Flipswitch JavaScript Web Provider](https://docs.flipswitch.io/docs/sdks/javascript) (JavaScript, Client, vendor supported) * [Flipswitch JavaScript Node.js Provider](https://docs.flipswitch.io/docs/sdks/javascript) (JavaScript, Server, vendor supported) * [Flipswitch Angular Web Provider](https://docs.flipswitch.io/docs/sdks/angular) (Angular, Client, vendor supported) * [Flipswitch NestJS Node.js Provider](https://docs.flipswitch.io/docs/sdks/nestjs) (NestJS, Server, vendor supported) * [Flipswitch React Web Provider](https://docs.flipswitch.io/docs/sdks/react) (React, Client, vendor supported) * [Flipswitch .NET Server Provider](https://docs.flipswitch.io/docs/sdks/dotnet) (.NET, Server, vendor supported) * [Flipswitch Go Server Provider](https://docs.flipswitch.io/docs/sdks/go) (Go, Server, vendor supported) * [Flipswitch Java Server Provider](https://docs.flipswitch.io/docs/sdks/java) (Java, Server, vendor supported) * [Flipswitch PHP Server Provider](https://docs.flipswitch.io/docs/sdks/php) (PHP, Server, vendor supported) * [Flipswitch Python Server Provider](https://docs.flipswitch.io/docs/sdks/python) (Python, Server, vendor supported) * [Flipswitch Ruby Server Provider](https://docs.flipswitch.io/docs/sdks/ruby) (Ruby, Server, vendor supported) * [Flipswitch Rust Server Provider](https://docs.flipswitch.io/docs/sdks/rust) (Rust, Server, vendor supported) * [Flipswitch Swift Client Provider](https://docs.flipswitch.io/docs/sdks/swift) (Swift, Client, vendor supported) ### Flipt * [Flipt Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flipt) (Go, Server, vendor supported) * [Flipt Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/flipt) (Java, Server, vendor supported) * [Flipt JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flipt-web) (JavaScript, Client, vendor supported) * [Flipt JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flipt) (JavaScript, Server, vendor supported) * [Flipt Rust Server Provider](https://github.com/open-feature/rust-sdk-contrib/tree/main/crates/flipt) (Rust, Server, vendor supported) * [Flipt Python Server Provider](https://github.com/open-feature/python-sdk-contrib/tree/main/providers/openfeature-provider-flipt) (Python, Server) ### GO Feature Flag * [GO Feature Flag Ruby Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_ruby) (Ruby, Server, vendor supported) * [GO Feature Flag JavaScript Node.js Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_javascript) (JavaScript, Server, vendor supported) * [GO Feature Flag JavaScript Web Provider](https://gofeatureflag.org/docs/sdk/client_providers/openfeature_javascript) (JavaScript, Client, vendor supported) * [GO Feature Flag Go Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_go) (Go, Server, vendor supported) * [GO Feature Flag Java Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_java) (Java, Server, vendor supported) * [GO Feature Flag .NET Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_dotnet) (.NET, Server, vendor supported) * [GO Feature Flag Python Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_python) (Python, Server, vendor supported) * [GO Feature Flag Kotlin Client Provider](https://gofeatureflag.org/docs/sdk/client_providers/openfeature_android) (Kotlin, Client, vendor supported) * [GO Feature Flag Swift Client Provider](https://gofeatureflag.org/docs/sdk/client_providers/openfeature_swift) (Swift, Client, vendor supported) * [GO Feature Flag PHP Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_php) (PHP, Server, vendor supported) * [GO Feature Flag NestJS Node.js Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_nestjs) (NestJS, Server, vendor supported) * [GO Feature Flag Angular Web Provider](https://gofeatureflag.org/docs/sdk/client_providers/openfeature_angular) (Angular, Client, vendor supported) * [GO Feature Flag React Web Provider](https://gofeatureflag.org/docs/sdk/client_providers/openfeature_react) (React, Client, vendor supported) * [GO Feature Flag Rust Server Provider](https://gofeatureflag.org/docs/sdk/server_providers/openfeature_rust) (Rust, Server, vendor supported) ### GrowthBook * [GrowthBook JavaScript Web Provider](https://docs.growthbook.io/lib/openfeature) (JavaScript, Client, vendor supported) * [GrowthBook JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/blob/main/libs/providers/growthbook/README.md) (JavaScript, Server, vendor supported) * [GrowthBook Python Server Provider](https://github.com/growthbook/growthbook-openfeature-provider-python) (Python, Server, vendor supported) * [GrowthBook Java Server Provider](https://github.com/growthbook/growthbook-openfeature-provider-java) (Java, Server, vendor supported) * [GrowthBook Go Server Provider](https://github.com/growthbook/growthbook-openfeature-provider-go) (Go, Server, vendor supported) * [GrowthBook .NET Server Provider](https://github.com/growthbook/growthbook-openfeature-provider-dot-net) (.NET, Server, vendor supported) ### Harness * [Harness Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/harness) (Go, Server) ### Hypertune * [Hypertune JavaScript Node.js Provider](https://www.npmjs.com/package/@hypertune/openfeature-server-provider) (JavaScript, Server, vendor supported) * [Hypertune JavaScript Web Provider](https://www.npmjs.com/package/@hypertune/openfeature-web-provider) (JavaScript, Client, vendor supported) ### Hyphen AI * [Hyphen AI .NET Server Provider](https://github.com/Hyphen/openfeature-provider-dotnet) (.NET, Server, vendor supported) * [Hyphen AI Go Server Provider](https://github.com/Hyphen/openfeature-provider-go) (Go, Server) * [Hyphen AI Java Server Provider](https://github.com/Hyphen/openfeature-provider-java) (Java, Server, vendor supported) * [Hyphen AI JavaScript Web Provider](https://github.com/Hyphen/openfeature-provider-javascript-web) (JavaScript, Client, vendor supported) * [Hyphen AI JavaScript Node.js Provider](https://github.com/Hyphen/openfeature-provider-javascript-server) (JavaScript, Server, vendor supported) * [Hyphen AI Python Server Provider](https://github.com/Hyphen/openfeature-provider-python) (Python, Server, vendor supported) * [Hyphen AI Swift Client Provider](https://github.com/Hyphen/hyphen-openfeature-swift) (Swift, Client, vendor supported) ### Intellitoggle * [Intellitoggle Dart Server Provider](https://github.com/aortem/intellitoggle-openfeature-provider-dart-server) (Dart, Server, vendor supported) ### Kameleoon * [Kameleoon .NET Server Provider](https://github.com/Kameleoon-new/openfeature-dotnet) (.NET, Server, vendor supported) * [Kameleoon Java Server Provider](https://github.com/Kameleoon-new/openfeature-java) (Java, Server, vendor supported) * [Kameleoon Python Server Provider](https://github.com/Kameleoon-new/openfeature-python) (Python, Server, vendor supported) * [Kameleoon Go Server Provider](https://github.com/Kameleoon-new/openfeature-go) (Go, Server, vendor supported) * [Kameleoon Kotlin Client Provider](https://github.com/Kameleoon-new/openfeature-android) (Kotlin, Client, vendor supported) * [Kameleoon Ruby Server Provider](https://github.com/Kameleoon-new/openfeature-ruby) (Ruby, Server, vendor supported) * [Kameleoon PHP Server Provider](https://github.com/Kameleoon-new/openfeature-php) (PHP, Server, vendor supported) * [Kameleoon Swift Client Provider](https://github.com/Kameleoon-new/openfeature-swift) (Swift, Client, vendor supported) * [Kameleoon JavaScript Node.js Provider](https://github.com/Kameleoon-new/openfeature-nodejs) (JavaScript, Server, vendor supported) * [Kameleoon JavaScript Web Provider](https://github.com/Kameleoon-new/openfeature-js) (JavaScript, Client, vendor supported) ### LaunchDarkly * [LaunchDarkly JavaScript Node.js Provider](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/openfeature-node-server) (JavaScript, Server, vendor supported) * [LaunchDarkly JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/launchdarkly-client) (JavaScript, Client) * [LaunchDarkly Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/launchdarkly) (Go, Server) * [LaunchDarkly Java Server Provider](https://github.com/launchdarkly/openfeature-java-server) (Java, Server, vendor supported) * [LaunchDarkly .NET Server Provider](https://github.com/launchdarkly/openfeature-dotnet-server) (.NET, Server, vendor supported) * [LaunchDarkly PHP Server Provider](https://github.com/launchdarkly/openfeature-php-server) (PHP, Server, vendor supported) * [LaunchDarkly Python Server Provider](https://github.com/launchdarkly/openfeature-python-server) (Python, Server, vendor supported) * [LaunchDarkly Ruby Server Provider](https://github.com/launchdarkly/openfeature-ruby-server) (Ruby, Server, vendor supported) ### Mixpanel * [Mixpanel Go Server Provider](https://github.com/mixpanel/mixpanel-go/tree/main/openfeature) (Go, Server, vendor supported) * [Mixpanel Java Server Provider](https://github.com/mixpanel/mixpanel-java/tree/master/openfeature-provider) (Java, Server, vendor supported) * [Mixpanel JavaScript Node.js Provider](https://github.com/mixpanel/mixpanel-node/tree/master/packages/openfeature-server-provider) (JavaScript, Server, vendor supported) * [Mixpanel JavaScript Web Provider](https://github.com/mixpanel/mixpanel-js/tree/master/packages/openfeature-web-provider) (JavaScript, Client, vendor supported) * [Mixpanel Kotlin Client Provider](https://github.com/mixpanel/mixpanel-android/tree/master/openfeature-provider) (Kotlin, Client, vendor supported) * [Mixpanel Python Server Provider](https://github.com/mixpanel/mixpanel-python/tree/master/openfeature-provider) (Python, Server, vendor supported) * [Mixpanel Ruby Server Provider](https://github.com/mixpanel/mixpanel-ruby/tree/master/openfeature-provider) (Ruby, Server, vendor supported) * [Mixpanel Swift Client Provider](https://github.com/mixpanel/mixpanel-swift-openfeature) (Swift, Client, vendor supported) ### MongoDB + Rules * [MongoDB + Rules Go Server Provider](https://github.com/ZackarySantana/openfeature-go) (Go, Server) ### Multi-Provider * [Multi-Provider JavaScript Node.js Provider](https://github.com/open-feature/js-sdk/tree/main/packages/server#multi-provider) (JavaScript, Server) * [Multi-Provider JavaScript Web Provider](https://github.com/open-feature/js-sdk/tree/main/packages/web#multi-provider) (JavaScript, Client) * [Multi-Provider Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multiprovider) (Java, Server) * [Multi-Provider .NET Server Provider](https://github.com/open-feature/dotnet-sdk/tree/main/src/OpenFeature.Providers.MultiProvider) (.NET, Server) ### Nona * [Nona JavaScript Node.js Provider](https://www.npmjs.com/package/nona-openfeature-provider) (JavaScript, Server, vendor supported) * [Nona JavaScript Web Provider](https://www.npmjs.com/package/nona-openfeature-web-provider) (JavaScript, Client, vendor supported) * [Nona .NET Server Provider](https://www.nuget.org/packages/Nona.OpenFeature.Provider) (.NET, Server, vendor supported) ### Octopus Deploy * [Octopus Deploy .NET Server Provider](https://github.com/OctopusDeploy/openfeature-provider-dotnet) (.NET, Server, vendor supported) * [Octopus Deploy Java Server Provider](https://github.com/OctopusDeploy/openfeature-provider-java) (Java, Server, vendor supported) * [Octopus Deploy JavaScript Web Provider](https://github.com/OctopusDeploy/openfeature-provider-ts-web) (JavaScript, Client, vendor supported) ### OFREP * [OFREP JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/ofrep-web) (JavaScript, Client, vendor supported) * [OFREP JavaScript Node.js Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/ofrep) (JavaScript, Server, vendor supported) * [OFREP Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/ofrep) (Go, Server, vendor supported) * [OFREP Python Server Provider](https://github.com/open-feature/python-sdk-contrib/tree/main/providers/openfeature-provider-ofrep) (Python, Server, vendor supported) * [OFREP .NET Server Provider](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Providers.Ofrep) (.NET, Server, vendor supported) * [OFREP Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/ofrep) (Java, Server, vendor supported) ### Optimizely * [Optimizely Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/optimizely) (Go, Server) * [Optimizely Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/optimizely) (Java, Server) ### Pendo * [Pendo JavaScript Node.js Provider](https://www.npmjs.com/package/@pendo/openfeature-server-provider) (JavaScript, Server, vendor supported) * [Pendo JavaScript Web Provider](https://www.npmjs.com/package/@pendo/openfeature-web-provider) (JavaScript, Client, vendor supported) ### PostHog * [PostHog JavaScript Node.js Provider](https://github.com/PostHog/posthog-js/tree/main/packages/openfeature-node-provider) (JavaScript, Server, vendor supported) * [PostHog JavaScript Web Provider](https://github.com/PostHog/posthog-js/tree/main/packages/openfeature-web-provider) (JavaScript, Client, vendor supported) * [PostHog Python Server Provider](https://github.com/PostHog/posthog-python/tree/main/openfeature-provider) (Python, Server, vendor supported) * [PostHog JavaScript Node.js Provider](https://www.npmjs.com/package/@tapico/node-openfeature-posthog) (JavaScript, Server) * [PostHog Go Server Provider](https://github.com/dhaus67/openfeature-posthog-go) (Go, Server) ### Prefab * [Prefab Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/prefab) (Go, Server) ### Reflag * [Reflag JavaScript Node.js Provider](https://github.com/reflagcom/javascript/tree/main/packages/openfeature-node-provider) (JavaScript, Server, vendor supported) * [Reflag JavaScript Web Provider](https://github.com/reflagcom/javascript/tree/main/packages/openfeature-browser-provider) (JavaScript, Client, vendor supported) ### Split * [Split JavaScript Node.js Provider](https://github.com/splitio/split-openfeature-provider-js) (JavaScript, Server, vendor supported) * [Split JavaScript Web Provider](https://github.com/splitio/split-openfeature-provider-web-js) (JavaScript, Client, vendor supported) * [Split Go Server Provider](https://github.com/splitio/split-openfeature-provider-go) (Go, Server, vendor supported) * [Split Java Server Provider](https://github.com/splitio/split-openfeature-provider-java) (Java, Server, vendor supported) * [Split .NET Server Provider](https://github.com/splitio/split-openfeature-provider-dotnet) (.NET, Server, vendor supported) * [Split PHP Server Provider](https://github.com/open-feature/php-sdk-contrib/tree/main/providers/Split) (PHP, Server) * [Split Swift Client Provider](https://github.com/splitio/split-openfeature-provider-swift) (Swift, Client, vendor supported) * [Split Kotlin Client Provider](https://github.com/splitio/split-openfeature-provider-android) (Kotlin, Client, vendor supported) ### Statsig * [Statsig .NET Server Provider](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.Statsig) (.NET, Server) * [Statsig Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/statsig) (Go, Server) * [Statsig Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/statsig) (Java, Server) ### Superposition * [Superposition Java Server Provider](https://github.com/juspay/superposition/tree/main/clients/java/openfeature-provider) (Java, Server, vendor supported) * [Superposition JavaScript Node.js Provider](https://github.com/juspay/superposition/tree/main/clients/javascript/open-feature-provider) (JavaScript, Server, vendor supported) * [Superposition Kotlin Client Provider](https://juspay.io/open-source/superposition/docs/providers/openfeature/java/) (Kotlin, Client, vendor supported) * [Superposition Python Server Provider](https://github.com/juspay/superposition/tree/main/clients/python/provider) (Python, Server, vendor supported) * [Superposition Rust Server Provider](https://github.com/juspay/superposition/tree/main/crates/superposition_provider) (Rust, Server, vendor supported) ### Switchbox * [Switchbox JavaScript Web Provider](https://github.com/ignat14/switchbox-sdk-js/tree/main/packages/openfeature) (JavaScript, Client, vendor supported) * [Switchbox React Web Provider](https://switchbox.dev/docs/sdk/openfeature) (React, Client, vendor supported) * [Switchbox Python Server Provider](https://github.com/ignat14/switchbox-sdk-python/tree/main/providers/openfeature) (Python, Server, vendor supported) ### Tggl * [Tggl JavaScript Node.js Provider](https://tggl.io/developers/sdks/open-feature/node) (JavaScript, Server, vendor supported) * [Tggl JavaScript Web Provider](https://tggl.io/developers/sdks/open-feature/web) (JavaScript, Client, vendor supported) * [Tggl .NET Server Provider](https://tggl.io/developers/sdks/open-feature/dotnet) (.NET, Server, vendor supported) ### Togul * [Togul JavaScript Node.js Provider](https://github.com/togulapp/togul-js#openfeature) (JavaScript, Server, vendor supported) * [Togul Go Server Provider](https://github.com/togulapp/togul-go#openfeature) (Go, Server, vendor supported) * [Togul Python Server Provider](https://github.com/togulapp/togul-python#openfeature) (Python, Server, vendor supported) * [Togul Ruby Server Provider](https://github.com/togulapp/togul-ruby#openfeature) (Ruby, Server, vendor supported) * [Togul PHP Server Provider](https://github.com/togulapp/togul-php#openfeature) (PHP, Server, vendor supported) ### Unleash * [Unleash Go Server Provider](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/unleash) (Go, Server) * [Unleash Java Server Provider](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/unleash) (Java, Server) * [Unleash JavaScript Web Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/unleash-web) (JavaScript, Client) * [Unleash Angular Web Provider](https://github.com/open-feature/js-sdk-contrib/blob/main/libs/providers/unleash-web/README.md#angular-framework) (Angular, Client) * [Unleash React Web Provider](https://github.com/open-feature/js-sdk-contrib/blob/main/libs/providers/unleash-web/README.md#react-framework) (React, Client) * [Unleash .NET Server Provider](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Providers.Unleash) (.NET, Server) * [Unleash JavaScript Node.js Provider](https://github.com/Unleash/unleash-openfeature-node-provider) (JavaScript, Server, vendor supported) * [Unleash Kotlin Client Provider](https://github.com/Unleash/unleash-openfeature-android-provider) (Kotlin, Client, vendor supported) * [Unleash PHP Server Provider](https://github.com/Unleash/unleash-openfeature-php-provider) (PHP, Server, vendor supported) * [Unleash Python Server Provider](https://github.com/Unleash/unleash-openfeature-python-provider) (Python, Server, vendor supported) * [Unleash Ruby Server Provider](https://github.com/Unleash/unleash-openfeature-ruby-provider) (Ruby, Server, vendor supported) * [Unleash Rust Server Provider](https://github.com/Unleash/unleash-openfeature-rust-provider) (Rust, Server, vendor supported) * [Unleash Swift Client Provider](https://github.com/Unleash/unleash-openfeature-swift-provider) (Swift, Client, vendor supported) ### UserDefaults * [UserDefaults Swift Client Provider](https://github.com/fumito-ito/UserDefaults-OpenFeature-Provider-Swift) (Swift, Client) ### Vercel Flags * [Vercel Flags JavaScript Node.js Provider](https://vercel.com/docs/flags/vercel-flags/sdks/openfeature) (JavaScript, Server, vendor supported) ### VWO * [VWO JavaScript Node.js Provider](https://github.com/wingify/vwo-openfeature-provider-node) (JavaScript, Server, vendor supported) * [VWO .NET Server Provider](https://github.com/wingify/vwo-openfeature-provider-dotnet) (.NET, Server, vendor supported) * [VWO Java Server Provider](https://github.com/wingify/vwo-openfeature-provider-java) (Java, Server, vendor supported) * [VWO PHP Server Provider](https://github.com/wingify/vwo-openfeature-provider-php) (PHP, Server, vendor supported) * [VWO Python Server Provider](https://github.com/wingify/vwo-openfeature-provider-python) (Python, Server, vendor supported) * [VWO Go Server Provider](https://github.com/wingify/vwo-openfeature-provider-go) (Go, Server, vendor supported) ## Hooks * [OpenTelemetry Trace Hook](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/hooks/open-telemetry) (JavaScript, Server) * [OpenTelemetry Trace Hook](https://github.com/open-feature/go-sdk-contrib/tree/main/hooks/open-telemetry) (Go, Server) * [OpenTelemetry Trace Hook](https://github.com/open-feature/java-sdk-contrib/tree/main/hooks/open-telemetry) (Java, Server) * [OpenTelemetry Trace Hook](https://github.com/open-feature/php-sdk-contrib/tree/main/hooks/OpenTelemetry) (PHP, Server) * [OpenTelemetry Trace Hook](https://github.com/open-feature/dotnet-sdk/tree/main/src/OpenFeature/Hooks) (.NET, Server) * [OpenTelemetry Trace Hook](https://github.com/open-feature/python-sdk-contrib/tree/main/hooks/openfeature-hooks-opentelemetry) (Python, Server) * [Validator Hook](https://github.com/open-feature/go-sdk-contrib/tree/main/hooks/validator) (Go, Server) * [Validator Hook](https://github.com/open-feature/php-sdk-contrib/tree/main/hooks/Validators) (PHP, Server) * [Datadog Hook](https://github.com/open-feature/php-sdk-contrib/tree/main/hooks/DDTrace) (PHP, Server) * [Sentry Hook](https://docs.sentry.io/platforms/javascript/configuration/integrations/openfeature/) (JavaScript, Client, vendor supported) * [Sentry Hook](https://docs.sentry.io/platforms/python/configuration/integrations/openfeature/) (Python, Client, vendor supported) * [Debounce Hook](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/hooks/debounce) (JavaScript, Server, Client) ## OFREP APIs * [ConfigCat OFREP API](https://configcat.com/docs/sdk-reference/openfeature/overview/#openfeature-remote-evaluation-protocol-ofrep) (vendor supported) * [DevCycle OFREP API](https://docs.devcycle.com/integrations/openfeature#openfeature-remote-evaluation-api) (vendor supported) * [flagd OFREP API](https://flagd.dev/reference/flagd-ofrep/?h=ofrep) (vendor supported) * [FFlags OFREP API](https://fflags.com) (vendor supported) * [Flaghoist OFREP API](https://docs.flaghoist.dev/api-reference/) (vendor supported) * [Flipswitch OFREP API](https://docs.flipswitch.io/docs/sdks/ofrep) (vendor supported) * [Flipt OFREP API](https://docs.flipt.io/integration/openfeature#remote-evaluation-protocol) (vendor supported) * [GO Feature Flag OFREP API](https://gofeatureflag.org/docs/sdk/ofrep) (vendor supported) * [Pydantic Logfire OFREP API](https://pydantic.dev/docs/logfire/manage/client-side-feature-flags/) (vendor supported) * [Togul OFREP API](https://togul.io/docs/en/api/ofrep) (vendor supported) ## Integrations * [Dropwizard Openfeature](https://github.com/sideshowcoder/dropwizard-openfeature) (Java, Server) * [FlagLint](https://github.com/flaglint/flaglint) (JavaScript, Server, vendor supported) --- # Evaluation API The Evaluation API is the primary component of OpenFeature that *application authors* interact with. The Evaluation API allows developers to evaluate feature flags to alter control flow and application characteristics. Video: ## Setting a Provider Before you can start evaluating flags, you must set a [*provider*](https://openfeature.dev/docs/reference/concepts/provider.md). The provider is the translation layer between the evaluation API and the flag system you use. **TypeScript** ```ts import { OpenFeature } from '@openfeature/server-sdk'; OpenFeature.setProvider(new YourProviderOfChoice()); ``` **Java** ```java import dev.openfeature.javasdk.OpenFeatureAPI; OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.setProvider(new YourProviderOfChoice()); ``` **C#** ```csharp using OpenFeature; Api.Instance.SetProvider(new YourProviderOfChoice()); ``` **Go** ```go openfeature.SetProvider(MyFeatureProvider{}) ``` **PHP** ```php use OpenFeature\OpenFeatureAPI; OpenFeatureAPI::getInstance()->setProvider(new YourProviderOfChoice()); ``` **Python** ```python import openfeature.api openfeature.api.set_provider(YourProviderOfChoice()) ``` ## Creating a client The OpenFeature client is a lightweight abstraction used to evaluate feature flags. If your application is small, you may use a single client for your whole application. In larger applications, it may be helpful to create multiple clients, each with different configuration to fit the needs of different sub-modules. Clients may also be created dynamically, per each HTTP request, for instance. **TypeScript** ```ts const client = OpenFeature.getClient('my-app'); ``` **Java** ```java Client client = api.getClient("my-app"); ``` **C#** ```csharp var client = Api.Instance.GetClient("my-app"); ``` **Go** ```go client := openfeature.NewClient("my-app") ``` **PHP** ```php $client = OpenFeatureAPI::getInstance()->getClient("my-app"); ``` **Python** ```python import openfeature.api openfeature.api.get_client("my-app") ``` ## Flag Evaluation ### Basic Evaluation The client can be used to do basic flag evaluation, which simply returns flag values of a particular type. The default value must also be specified. In the case of any error during flag evaluation, the default value will be returned, so give consideration to your default values! **TypeScript** ```ts // get a bool value const boolValue = await client.getBooleanValue('boolFlag', false); // get a string value const stringValue = await client.getStringValue('stringFlag', 'default'); // get a numeric value const numberValue = await client.getNumberValue('intFlag', 1); // get an object value const object = await client.getObjectValue('objectFlag', {}); ``` **Java** ```java // get a boolean value Boolean boolValue = client.getBooleanValue("boolFlag", false); // get a string value String stringValue = client.getStringValue("stringFlag", "default"); // get an integer value Integer intValue = client.getIntegerValue("intFlag", 1); // get a double value Double doubleValue = client.getDoubleValue("doubleFlag", 0.9); // get an object value Value objectValue = client.getObjectValue("objectFlag", MyObjectInstance); ``` **C#** ```csharp // get a boolean value var boolValue = await client.GetBooleanValue("boolFlag", false); // get a string value var stringValue = await client.GetStringValue("stringFlag", "default"); // get an integer value var intValue = await client.GetIntegerValue("intFlag", 1); // get a double value var doubleValue = await client.GetDoubleValue("doubleFlag", 1); // get an object value var objectValue = await client.GetObjectValue("objectFlag", MyObjectInstance); ``` **Go** ```go // get a boolean value boolValue, err := client.BooleanValue(context.Background(), "boolFlag", false, openfeature.EvaluationContext{}) // get a string value stringValue, err := client.StringValue(context.Background(), "stringFlag", "default", openfeature.EvaluationContext{}) // get an integer value intValue, err := client.IntValue(context.Background(), "intFlag", 1, openfeature.EvaluationContext{}) // get a float value floatValue, err := client.FloatValue(context.Background(), "floatFlag", 3.14, openfeature.EvaluationContext{}) // get an object value objectValue, err := client.ObjectValue(context.Background(), "objectFlag", myObject, openfeature.EvaluationContext{}) ``` **PHP** ```php // get a boolean value $boolValue = $client->getBooleanValue("boolFlag", false, null, null); // get a string value $stringValue = $client->getStringValue("stringFlag", "default", null, null); // get an integer value $intValue = $client->getIntegerValue("intFlag", 1, null, null); // get a float value $floatValue = $client->getFloatValue("floatFlag", 0.9, null, null); // get an object value $objectValue = $client->getObjectValue("objectFlag", $myObjectInstance, null, null); ``` **Python** ```python # get a boolean value value = client.get_boolean_value("boolFlag", False) # get a string value value = client.get_string_value("stringFlag", "default") # get an integer value value = client.get_integer_value("intFlag", 1) # get a float value value = client.get_float_value("floatFlag", 0.9) # get an object value value = client.get_object_value("objectFlag", my_object_instance) ``` ### Detailed Evaluation In addition to [basic evaluation](#basic-evaluation), *detailed evaluation* methods are available. These return the value, as well as additional metadata about the flag evaluation in the *Evaluation Details* structure. #### Evaluation Details Structure Fields | Field | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------ | | flag key | the unique identifier for a feature flag | | value | the value returned from a flag evaluation | | reason (optional) | a string explaining why the flag value was returned | | variant (optional) | the variant associated with the return flag value | | flag metadata (optional) | a key-value structure which supports definition of arbitrary properties | | error code (optional) | an [error code](https://openfeature.dev/specification/types.md#error-code) that categorizes flag evaluation errors | | error message (optional) | a string detailing the error | **TypeScript** ```ts // get details of boolean evaluation const boolDetails = await client.getBooleanDetails('boolFlag', false); // get details of string evaluation const stringDetails = await client.getStringDetails('stringFlag', 'default'); // get details of numeric evaluation const numberDetails = await client.getNumberDetails('intFlag', 1); // get details of object evaluation const objectDetails = await client.getObjectDetails('objectFlag', {}); ``` **Java** ```java // get details of boolean evaluation FlagEvaluationDetails boolDetails = client.getBooleanDetails("boolFlag", false); // get details of string evaluation FlagEvaluationDetails stringDetails = client.getStringDetails("stringFlag", "default"); // get details of integer evaluation FlagEvaluationDetails intDetails = client.getIntegerDetails("intFlag", 1); // get details of double evaluation FlagEvaluationDetails doubleDetails = client.getDoubleDetails("doubleFlag", .9); // get details of object evaluation FlagEvaluationDetails objectDetails = client.getObjectDetails("objectFlag", myObjectDefaultInstance); ``` **C#** ```csharp // get details of boolean evaluation var boolDetails = await client.GetBooleanDetails("boolFlag", false); // get details of string evaluation var stringDetails = await client.GetStringDetails("stringFlag", "default"); // get details of int evaluation var intDetails = await client.GetIntegerDetails("intFlag", 1); // get details of double evaluation var doubleDetails = await client.GetDoubleDetails("doubleFlag", 1); // get details of object evaluation var objectDetails = await client.GetObjectDetails("objectFlag", myObjectDefaultInstance); ``` **Go** ```go // get details of boolean evaluation boolDetails, err := client.BooleanValueDetails(context.Background(), "boolFlag", false, openfeature.EvaluationContext{}) // get details of string evaluation stringDetails, err := client.StringValueDetails(context.Background(), "stringFlag", "default", openfeature.EvaluationContext{}) // get details of integer evaluation intDetails, err := client.IntValueDetails(context.Background(), "intFlag", 1, openfeature.EvaluationContext{}) // get details of float evaluation floatDetails, err := client.FloatValueDetails(context.Background(), "floatFlag", 3.14, openfeature.EvaluationContext{}) // get details of object evaluation objectDetails, err := client.ObjectValueDetails(context.Background(), "objectFlag", myObject, openfeature.EvaluationContext{}) ``` **PHP** ```php // get details of a boolean evaluation $boolDetails = $client->getBooleanDetails("boolFlag", false, null, null); // get details of a string evaluation $stringDetails = $client->getStringDetails("stringFlag", "default", null, null); // get details of an integer evaluation $intDetails = $client->getIntegerDetails("intFlag", 1, null, null); // get details of a float evaluation $floatDetails = $client->getFloatDetails("floatFlag", 0.9, null, null); // get details of an object evaluation $objectDetails = $client->getObjectDetails("objectFlag", $myObjectInstance, null, null); ``` **Python** ```python # get details of boolean evaluation details = client.get_boolean_details("boolFlag", False) # get details of string evaluation details = client.get_string_details("stringFlag", "default") # get details of an integer evaluation details = client.get_integer_details("intFlag", 1) # get details of a float evaluation details = client.get_float_details("floatFlag", 1) # get details of object evaluation details = client.get_object_details("objectFlag", {}) ``` --- # Evaluation Context The [*evaluation context*](https://openfeature.dev/specification/glossary.md#evaluation-context) is a container for arbitrary contextual data that can be used as a basis for dynamic evaluation. Static data such as the host or an identifier for the application can be configured globally. Dynamic evaluation context, such as the IP address of the client in a web application, can be implicitly propagated or explicitly passed during flag evaluation, and can be merged with static values. Video: ## Providing Evaluation Context ### Dynamic Context Implementations (Server-side SDKs) In [server-side SDKs](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md#dynamic-context-paradigm-server-side-sdks), values relevant for flag evaluation can be included in the evaluation context at multiple points: globally (on the top level API), on the client, and at the point of flag evaluation (invocation). **TypeScript** ```ts // add a value to the global context OpenFeature.setContext({ myGlobalKey: 'myGlobalValue' }); // add a value to the client context const client = OpenFeature.getClient(); client.setContext({ myClientKey: 'myClientValue' }); // add a value to the invocation context const context: EvaluationContext = { myInvocationKey: 'myInvocationValue', }; const boolValue = await client.getBooleanValue('boolFlag', false, context); ``` **Java** ```java // add a value to the global context OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.setEvaluationContext(new MutableContext().add("myGlobalKey", "myGlobalValue")); // add a value to the client context Client client = api.getClient(); client.setEvaluationContext(new MutableContext().add("myClientKey", "myClientValue")); // add a value to the invocation context EvaluationContext context = new MutableContext(); context.addStringAttribute("myInvocationKey", "myInvocationValue") Boolean boolValue = client.getBooleanValue("boolFlag", false, context); ``` **C#** ```csharp // add a value to the global context Api api = Api.Instance; api.SetContext(new EvaluationContextBuilder().Set("myGlobalKey", "myGlobalValue").Build()); // add a value to the client context FeatureClient client = api.GetClient(); client.SetContext(new EvaluationContextBuilder().Set("myClientKey", "myClientValue").Build()); // add a value to the invocation context var context = new EvaluationContext() .Set("myInvocationKey", "myInvocationValue") .Build(); var boolValue = await client.GetBooleanValue("boolFlag", false, context); ``` **Go** ```go // add a value to the global context openfeature.SetEvaluationContext(openfeature.NewEvaluationContext( "", map[string]interface{}{ "myGlobalKey": "myGlobalValue", }, )) // add a value to the client context client := openfeature.NewClient("my-app") client.SetEvaluationContext(openfeature.NewEvaluationContext( "", map[string]interface{}{ "myGlobalKey": "myGlobalValue", }, )) // add a value to the invocation context evalCtx := openfeature.NewEvaluationContext( "", map[string]interface{}{ "myInvocationKey": "myInvocationValue", }, ) boolValue, err := client.BooleanValue("boolFlag", false, evalCtx) ``` **PHP** ```php // add a value to the global context $api = OpenFeatureAPI::getInstance(); $api->setEvaluationContext(new EvaluationContext("targetingKey", ["myGlobalKey" => "myGlobalValue"])); // add a value to the client context $client = $api->getClient(); $client->setEvaluationContext(new EvaluationContext("targetingKey", ["myClientKey" => "myClientValue"])); // add a value to the invocation context $context = new EvaluationContext("targetingKey", ["myInvocationKey" => "myInvocationValue"]); $boolValue = $client->getBooleanValue("boolFlag", false, $context); ``` **Python** ```python # add a value to the global context import openfeature.api context = EvaluationContext("targetingKey", {"myGlobalKey": "myGlobalValue"}) openfeature.api.set_evaluation_context(context) # add a value to the client context client = api.get_client() context = EvaluationContext("targetingKey", {"myClientKey": "myClientValue"}) client.set_evaluation_context(context) # add a value to the invocation context context = EvaluationContext("targetingKey", {"myInvocationKey": "myInvocationValue"}) bool_value = client.get_boolean_value("boolFlag", False, context) ``` #### Context merging At the point of flag evaluation, the evaluation context is merged, and duplicate values are overwritten as defined in the [specification](https://openfeature.dev/specification/sections/evaluation-context.md#32-context-levels-and-merging). ### Static Context Implementations (Client-side SDKs) In [client-side SDKs](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md#static-context-paradigms-client-side-sdks), values relevant for flag evaluation are set on the OpenFeature API object. In these implementations, this is an asynchronous operation associated with [provider reconciliation](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md#static-context-paradigms-client-side-sdks). **TypeScript** ```ts // add a value to the context await OpenFeature.setContext({ myUserData: 'myUserValue' }); // the context is used for all feature flag evaluations automatically. const boolValue = await client.getBooleanValue('boolFlag', false); ``` ## Circular structures in Evaluation Context Many providers serialize the evaluation context as part of their operation. Be careful not to include circular structures in the evaluation context to avoid serialization issues. ## Targeting Key Many feature flag management systems require an identifier for the subject of flag evaluation. For many feature flag systems this is required in order to perform fractional evaluation or percentage-based rollouts deterministically. In the case of web applications or mobile apps, the subject is frequently an end user, but in other cases it could be a service or client application. The `evaluation context` includes an optional `targeting key` field for this purpose. The targeting key should contain a string uniquely identifying the subject (i.e.: a UUID, a hash of some user attribute such as an email, or the hostname of an application or service). Some [providers](https://openfeature.dev/docs/reference/concepts/provider.md) may require this field to be set to function correctly. ## Personally Identifiable Information (PII) Considerations Be thoughtful in your inclusion of personal data in the `evaluation context`. Such data is useful for targeting and dynamic evaluation, but you should consider how the [provider](https://openfeature.dev/docs/reference/concepts/provider.md) in use may handle or persist this data. Hooks (specifically hooks implementing the *before* stage) can be useful to restrict, filter or anonymize data in the `evaluation context`. --- # Events Events enable the ability to react to state changes in the provider or underlying flag management system. These include changes in provider readiness, error status, or perhaps most interestingly, flag configuration changes. ## Event Handlers Event handlers can be attached to a `client` or to the global API object, for any [provider event type](https://openfeature.dev/specification/types.md#provider-events). Handlers attached to the global API object run when *any* provider emits the associated events; this makes global handlers ideal for configuration troubleshooting, monitoring and other cross-cutting concerns. Handlers attached to a client will run only when the provider bound with that client emits the associated event; this makes `client` handlers ideal for reacting to flag-state changes in an application domain logic. Handlers are passed an [event details](https://openfeature.dev/specification/types.md#event-details) structure, which contains data about the event, including a list of keys that have changed (if applicable and available). **TypeScript** ```ts // add an event handler to a client const client = OpenFeature.getClient(); client.addHandler(ProviderEvents.ConfigurationChanged, (eventDetails) => { // do something when the provider's flag settings change }); // add an event handler to the global API OpenFeature.addHandler(ProviderEvents.Error, (eventDetails) => { // do something when the provider goes into an error state }); ``` **Java** ```java // add an event handler to a client Client client = OpenFeatureAPI.getInstance().getClient(); client.onProviderConfigurationChanged((EventDetails eventDetails) -> { // do something when the provider's flag settings change }); // add an event handler to the global API OpenFeatureAPI.getInstance().onProviderError((EventDetails eventDetails) -> { // do something when the provider goes into an error state }); ``` **C#** ```csharp // add an event handler to a client var client = Api.Instance.GetClient(); client.AddHandler(ProviderEventTypes.ProviderConfigurationChanged, (ProviderEventPayload eventDetails) => { // do something when the provider's flag settings change }); // add an event handler to the global API Api.Instance.AddHandler(ProviderEventTypes.ProviderError, (ProviderEventPayload eventDetails) => { // do something when the provider goes into an error state }); ``` **Go** ```go var changedCallback = func(details openfeature.EventDetails) { // do something when the provider's flag settings change } // add an event handler to a client client := openfeature.NewClient("clientName") // add an event handler to a client client.AddHandler(openfeature.ProviderError, &changedCallback) var errorCallback = func(details openfeature.EventDetails) { // do something when the provider goes into an error state } // add an event handler to the global API openfeature.AddHandler(openfeature.ProviderError, &errorCallback) ``` **Python** ```python client = api.get_client() def on_changed(event_details: EventDetails): # do something when the provider's flag settings change # add an event handler to a client client.add_handler(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, on_changed) def on_error(event_details: EventDetails): # do something when the provider goes into an error state # add an event handler to the global API api.add_handler(ProviderEvent.PROVIDER_ERROR, on_error) ``` ## Event Types See [event types](https://openfeature.dev/specification/types.md#provider-events) specification. ### PROVIDER_READY The provider is ready to perform flag evaluations. *Application authors* may wish to wait to evaluate flags until the provider has fully started. In that case, they can await the `PROVIDER_READY` event before evaluating any flags. This can be especially useful for hiding UI elements or otherwise deferring evaluation until associated flags can be resolved accurately. ### PROVIDER_CONFIGURATION_CHANGED A change was made to the backend flag configuration. If the flag management system supports it, providers may emit a `PROVIDER_CONFIGURATION_CHANGED` event signifying that flag configurations have changed. This might mean the value of a flag (or flags) have changed, or that a rule has been updated. Generally, the associated event details will indicate which flag (or flags) have been changed with the `flags changed` field. ### PROVIDER_ERROR The provider signaled an error. If a provider becomes unavailable, evaluations will typically default. Handlers associated with `PROVIDER_ERROR` events can be used to alert monitoring systems about the provider failure, or enable custom fallback mechanisms. ### PROVIDER_STALE The provider's cached state is no longer valid and may not be up-to-date with the source of truth. Some providers maintain a connection to a management system, but are also tolerant of disconnection. The `PROVIDER_STALE` indicates this situation. ### PROVIDER_RECONCILING (Static-context/Client-side only) The context associated with the provider has changed, and the provider has not yet [reconciled](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md#static-context-paradigms-client-side-sdks) its associated state. ### PROVIDER_CONTEXT_CHANGED (Static-context/Client-side only) The context associated with the provider has changed, and the provider has [reconciled](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md#static-context-paradigms-client-side-sdks) its associated state. --- # Hooks *Hooks* are a mechanism that allow for the addition of arbitrary behavior at well-defined points of the *flag evaluation life-cycle*. Use cases include validation of the resolved flag value, modifying or adding data to the evaluation context, logging, telemetry, and tracking. Video: ## Adding Hooks to Flag Evaluation Hooks can be attached globally (on the OpenFeature API object), at the client, or at the flag invocation level. **TypeScript** ```ts // add a hook globally, to run on all evaluations OpenFeature.addHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client const client = OpenFeature.getClient(); client.addHooks(new ExampleClientHook()); // add a hook for this evaluation only const value = await client.getBooleanValue(FLAG_KEY, false, context, { hooks: [new ExampleInvocationHook()], }); ``` **Java** ```java // add a hook globally, to run on all evaluations OpenFeatureAPI.getInstance().addHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client Client client = api.getClient(); client.addHooks(new ExampleClientHook()); // add a hook for this evaluation only Boolean value = client.getBooleanValue("key", false, null, FlagEvaluationOptions .builder() .hook(new ExampleInvocationHook()) .build()); ``` **C#** ```csharp // add a hook globally, to run on all evaluations Api.Instance.AddHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client var client = Api.Instance.GetClient(); client.AddHooks(new ExampleClientHook()); // add a hook for this evaluation only var value = await client.GetBooleanValue("boolFlag", false, context, new FlagEvaluationOptions(new ExampleInvocationHook())); ``` **Go** ```go // add a hook globally, to run on all evaluations openfeature.AddHooks(ExampleGlobalHook{}) // add a hook on this client, to run on all evaluations made by this client client := openfeature.NewClient("my-app") client.AddHooks(ExampleClientHook{}) // add a hook for this evaluation only value, err := client.BooleanValue( context.Background(), "boolFlag", false, openfeature.EvaluationContext{}, WithHooks(ExampleInvocationHook{}), ) ``` **PHP** ```php // add a hook globally, to run on all evaluations $api = OpenFeatureAPI::getInstance(); $api->addHook(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client $client = $api->getClient(); $client->addHook(new ExampleClientHook()); // add a hook for this evaluation only $value = $client->getBooleanValue("boolFlag", false, $context, new EvaluationOptions([new ExampleInvocationHook()])); ``` **Python** ```python # add a hook globally, to run on all evaluations import openfeature.api openfeature.api.add_hooks([ExampleGlobalHook()]) # add a hook on this client, to run on all evaluations made by this client client = api.get_client() client.add_hooks([ExampleClientHook()]) # add a hook for this evaluation only options = EvaluationOptions(hooks=[ExampleInvocationHook()]) value = client.get_boolean_value("boolFlag", False, context, options) ``` ## Flag Evaluation Life-Cycle The flag evaluation life-cycle consists of four stages: *Before*, *After*, *Error* and *Finally*. A Hook will implement one or more of these stages, performing activities related to their specific role. > Note: "finally" is a reserved word in some languages. In such cases, an alternate name is used, such as *finallyAfter*. ### Before The *Before* stage is evaluated before flag evaluation takes place. Hooks implementing the Before stage may alter the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) or perform other side effects. Note that Before hooks run before flag evaluation has succeeded. Consider this when using Before hooks. You may want to use [After hooks](#after) for use cases where you want to perform some side effect with an assurance that flag evaluation proceeded normally. ### After The *After* stage is evaluated after **successful** flag evaluation. Hooks implementing the After stage may validate the resolved flag value (throwing or returning errors) or perform other side effects. After hooks are an ideal mechanism for publishing records in other services for the purposes of telemetry relating to flag evaluation or for user tracking. ### Error The *Error* stage runs only in the case that flag evaluation has proceeded abnormally. Some reasons for abnormal execution include: * the flag control plane could not be reached * the flag referenced by the flag key does not exist * the flag referenced by the flag key is not of the expected type * an unhandled error occurred in a *Before* hook The Error stage is ideal for logging error messages or adding error records to a telemetry provider. ### Finally The *Finally* stage runs unconditionally, no matter if flag resolution succeeded or failed. Hooks implementing this stage might use it to release resources or finalize records. ### Ordering Hooks are evaluated in the following order: * before: API, Client, Invocation * after: Invocation, Client, API * error (if applicable): Invocation, Client, API * finally: Invocation, Client, API Hooks added at the same level (API, client, or invocation) are evaluated in the order they are added. ## Implementing Hooks Create a hook by implementing **at least** one of the flag evaluation life-cycle stages. **TypeScript** ```ts export class MyHook implements Hook { before(hookContext: HookContext) { // code to run before flag evaluation } after(hookContext: HookContext, details: EvaluationDetails) { // code to run after successful flag evaluation } error(hookContext: HookContext, err: Error) { // code to run if there's an error during before hooks or during flag evaluation } finally(hookContext: HookContext, details: EvaluationDetails) { // code to run after all other stages, regardless of success/failure } } ``` **Java** ```java class MyHook implements BooleanHook { @Override public Optional before(HookContext ctx, Map hints) { // code to run before flag evaluation } @Override public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) { // code to run after successful flag evaluation } @Override public void error(HookContext ctx, Exception error, Map hints) { // code to run if there's an error during before hooks or during flag evaluation } @Override public void finallyAfter(HookContext ctx, FlagEvaluationDetails details, Map hints) { // code to run after all other stages, regardless of success/failure } } ``` **C#** ```csharp MyHook : Hook { public Task Before(HookContext context, IReadOnlyDictionary hints = null) { // code to run before flag evaluation } public virtual Task After(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null) { // code to run after successful flag evaluation } public virtual Task Error(HookContext context, Exception error, IReadOnlyDictionary hints = null) { // code to run if there's an error during before hooks or during flag evaluation } public virtual Task Finally(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null) { // code to run after all other stages, regardless of success/failure } } ``` **Go** ```go type myHook struct{} func (h myHook) Before(ctx context.Context, hookContext openfeature.HookContext, hookHints openfeature.HookHints) (*openfeature.EvaluationContext, error) { // code to run before flag evaluation } func (h myHook) After(ctx context.Context, hookContext openfeature.HookContext, details openfeature.InterfaceEvaluationDetails, hookHints openfeature.HookHints) error { // code to run after successful flag evaluation } func (h myHook) Error(ctx context.Context, hookContext openfeature.HookContext, err error, hookHints openfeature.HookHints) { // code to run if there's an error during before hooks or during flag evaluation } func (h myHook) Finally(ctx context.Context, hookContext openfeature.HookContext, details openfeature.InterfaceEvaluationDetails, hookHints openfeature.HookHints) { // code to run after all other stages, regardless of success/failure } ``` **PHP** ```php class MyHook implements Hook { public function before(HookContext $context, HookHints $hints): ?EvaluationContext { // code to run before flag evaluation } public function after(HookContext $context, ResolutionDetails $details, HookHints $hints): void { // code to run after successful flag evaluation } public function error(HookContext $context, Throwable $error, HookHints $hints): void { // code to run if there's an error during before hooks or during flag evaluation } public function finally(HookContext $context, HookHints $hints): void { // code to run after all other stages, regardless of success/failure } } ``` **Python** ```python from openfeature.hook import Hook, HookHints class MyHook(Hook): def before(self, context: HookContext, hints: HookHints): ... # code to run before flag evaluation def after(self, context: HookContext, details: FlagEvaluationDetails, hints: HookHints): ... # code to run after successful flag evaluation def error(self, context: HookContext, error, hints: HookHints): ... # code to run if there's an error during before hooks or during flag evaluation def finally_after(self, context: HookContext, details: FlagEvaluationDetails, hints: HookHints): ... # code to run after all other stages, regardless of success/failure ``` --- # Providers *Providers* are responsible for performing flag evaluations. They provide an abstraction between the underlying flag management system and the OpenFeature SDK. Providers can wrap a vendor SDK, call a bespoke flag evaluation REST API, or even parse some locally stored file to resolve flag values. This allows the underlying flag evaluation logic to be changed without requiring a major code refactor. An [application integrator](https://openfeature.dev/specification/glossary.md#application-integrator) can register one provider at a time. Registering an additional provider will override any previously configured providers. If no provider is set, OpenFeature will [no-op](https://en.wikipedia.org/wiki/NOP_\(code\)) and return the default value passed to the evaluation API. Providers are set through the [evaluation API](https://openfeature.dev/docs/reference/concepts/evaluation-api.md#setting-a-provider). They're globally registered and a change affects both new and existing OpenFeature clients. ![](/assets/images/of-architecture-a49b167df4037d936bd6623907d84de1.png) *An example of an OpenFeature provider for a fictional Flags-R-us service.* Video: ## Implementing Providers ### Getting started To develop a provider, start by confirming that an OpenFeature SDK is available in the language you're interested in. Next, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in an [existing contrib repository](https://github.com/open-feature?q=contrib\&type=all\&language=\&sort=) available under the OpenFeature organization. Finally, you’ll then need to write the provider itself. In most languages, this can be accomplished by implementing the provider interface exported by the OpenFeature SDK. ### Naming recommendations The following naming recommendations can be used to ensure consistency between providers developed among the community. #### Repository name If you're creating a new repository, it's recommended that you use one of the following naming conventions: * `openfeature-provider-` * `-openfeature-provider-` note It is **not** required that a provider be open source, but it's highly recommended. #### Artifact name The name of the artifact will vary based on the package manager. Ultimately, it's up to the publisher to decide what name makes the most sense. However, the following items should be used in the package name: * OpenFeature (can also be stylized as openfeature or open-feature) * Vendor/tool name * The word `provider` should be included in the name An example of what it could look like in NPM: `@openfeature/flagd-provider` ### Domain scoped providers OpenFeature supports multiple providers [domain-scoped](https://openfeature.dev/specification/glossary/#domain) to multiple clients in a single application. This allows teams to scope flag evaluation to a particular flag management system, making it easier for developers to mix and match providers based on their needs, all from a unified SDK. ```typescript OpenFeature.setProvider('ProviderA', new ProviderA()); OpenFeature.setProvider('ProviderB', new ProviderB()); const clientA = OpenFeature.getClient('ProviderA'); const clientB = OpenFeature.getClient('ProviderB'); ``` > To learn more, please refer to the [OpenFeature Enhancement Proposal](https://github.com/open-feature/ofep/blob/main/OFEP/provider-client-mapping.md) or [spec change](https://openfeature.dev/specification/sections/flag-evaluation/#requirement-113). ### Multi-Provider support OpenFeature SDKs also support combining multiple providers under a single client using the [Multi-Provider specification](https://openfeature.dev/specification/appendix-a.md#multi-provider). This interface allows your application to transparently interact with multiple providers from different flag management systems through a single interface. The logic of how the different providers are accessed through the multi-provider is handled through the assigned [multi-provider strategy](https://openfeature.dev/specification/appendix-a.md#strategies). ```typescript const multiProvider = new MultiProvider( [ { provider: new ProviderA(), }, { provider: new ProviderB(), }, ], new FirstMatchStrategy(), ); await OpenFeature.setProviderAndWait(multiProvider); ``` ### Examples #### Dynamic Context Implementations (Server-side SDKs) **TypeScript** ```ts import { Provider, ResolutionDetails, EvaluationContext, JsonValue, OpenFeatureEventEmitter, } from '@openfeature/server-sdk'; export class MyFeatureProvider implements Provider { readonly metadata = { name: 'My Feature Provider', } as const; readonly runsOn = 'server'; // emitter for provider events events = new OpenFeatureEventEmitter(); resolveBooleanEvaluation( flagKey: string, defaultValue: boolean, context: EvaluationContext, ): Promise> { // code to resolve boolean details } resolveStringEvaluation( flagKey: string, defaultValue: string, context: EvaluationContext, ): Promise> { // code to resolve string details } resolveNumberEvaluation( flagKey: string, defaultValue: number, context: EvaluationContext, ): Promise> { // code to resolve number details } resolveObjectEvaluation( flagKey: string, defaultValue: JsonValue, context: EvaluationContext, ): Promise> { // code to resolve object details } } ``` **Java** ```java package dev.openfeature.javasdk; public class MyFeatureProvider implements FeatureProvider { private final String name = "My Feature Provider"; @Override public String getName() { return this.name; } @Override public Metadata getMetadata() { return new Metadata() { @Override public String getName() { return name; } }; } @Override public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { // code to resolve boolean details } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { // code to resolve string details } @Override public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { // code to resolve integer details } @Override public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { // code to resolve double details } @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext invocationContext) { // code to resolve object details } } ``` **C#** ```csharp using System.Threading.Tasks; using OpenFeature; using OpenFeature.Model; public class MyFeatureProvider : FeatureProvider { public static string Name => "My Feature Provider"; public override Metadata GetMetadata() { return new Metadata(Name); } public override Task> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null) { // code to resolve boolean details } public override Task> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null) { // code to resolve string details } public override Task> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null) { // code to resolve integer details } public override Task> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null) { // code to resolve double details } public override Task> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null) { // code to resolve object details } } ``` **Go** ```go package provider // MyFeatureProvider implements the FeatureProvider interface and provides functions for evaluating flags type MyFeatureProvider struct{} // Metadata returns the metadata of the provider func (e MyFeatureProvider) Metadata() Metadata { return Metadata{Name: "MyFeatureProvider"} } // BooleanEvaluation returns a boolean flag func (e MyFeatureProvider) BooleanEvaluation(flag string, defaultValue bool, evalCtx EvaluationContext) BoolResolutionDetail { // code to evaluate boolean } // StringEvaluation returns a string flag func (e MyFeatureProvider) StringEvaluation(flag string, defaultValue string, evalCtx EvaluationContext) StringResolutionDetail { // code to evaluate string } // FloatEvaluation returns a float flag func (e MyFeatureProvider) FloatEvaluation(flag string, defaultValue float64, evalCtx EvaluationContext) FloatResolutionDetail { // code to evaluate float } // IntEvaluation returns an int flag func (e MyFeatureProvider) IntEvaluation(flag string, defaultValue int64, evalCtx EvaluationContext) IntResolutionDetail { // code to evaluate int } // ObjectEvaluation returns an object flag func (e MyFeatureProvider) ObjectEvaluation(flag string, defaultValue interface{}, evalCtx EvaluationContext) ResolutionDetail { // code to evaluate object } // Hooks returns hooks func (e MyFeatureProvider) Hooks() []Hook { // code to retrieve hooks } ``` **PHP** ```php namespace OpenFeature\Dev\MyFeature; use OpenFeature\interfaces\hooks\HooksAwareTrait; use OpenFeature\interfaces\provider\Provider; use Psr\Log\LoggerAwareTrait; class MyFeatureProvider implements Provider { use HooksAwareTrait; use LoggerAwareTrait; public const NAME = 'MyFeatureProvider'; public function getMetadata(): MetadataInterface { return new Metadata(self::NAME); } public function resolveBooleanValue(string $flagKey, bool $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // code to evaluate boolean } public function resolveStringValue(string $flagKey, string $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // code to evaluate string } public function resolveIntegerValue(string $flagKey, int $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // code to evaluate integer } public function resolveFloatValue(string $flagKey, float $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // code to evaluate float } public function resolveObjectValue(string $flagKey, $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // code to evaluate object } } ``` **Python** ```python from typing import List, Optional from openfeature.evaluation_context import EvaluationContext from openfeature.flag_evaluation import FlagResolutionDetails from openfeature.provider.provider import AbstractProvider from openfeature.provider.metadata import Metadata class MyProvider(AbstractProvider): def get_metadata(self) -> Metadata: return Metadata(name="MyFeatureProvider") def get_provider_hooks(self) -> List[Hook]: return [] def resolve_boolean_details( self, flag_key: str, default_value: bool, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[bool]: ... # code to evaluate boolean def resolve_string_details( self, flag_key: str, default_value: str, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[str]: ... # code to evaluate string def resolve_integer_details( self, flag_key: str, default_value: int, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[int]: ... # code to evaluate integer def resolve_float_details( self, flag_key: str, default_value: float, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[float]: ... # code to evaluate float def resolve_object_details( self, flag_key: str, default_value: Union[dict, list], evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[Union[dict, list]]: ... # code to evaluate object ``` ### Static Context Implementations (Client-side SDKs) **TypeScript** ```ts import { EvaluationContext, Provider, JsonValue, ResolutionDetails } from '@openfeature/web-sdk'; export class TestProvider implements Provider { metadata = { name: TestProvider.name, }; readonly runsOn = 'client'; hooks = []; onContextChange(oldContext: EvaluationContext, newContext: EvaluationContext): Promise { // code to run to reconcile the providers state with the newly updated context } resolveBooleanEvaluation(flagKey: string, defaultValue: boolean): ResolutionDetails { // code to evaluate boolean } resolveStringEvaluation(flagKey: string, defaultValue: string): ResolutionDetails { // code to evaluate string } resolveNumberEvaluation(flagKey: string, defaultValue: number): ResolutionDetails { // code to evaluate number } resolveObjectEvaluation(flagKey: string, defaultValue: U): ResolutionDetails { // code to evaluate object } } ``` ### Checklist Here is a checklist of important considerations when implementing a provider. #### Functional requirements All resolution methods are implemented (see `Provider` / `FeatureProvider` interface in applicable SDK). Relevant configuration options for the underlying backend are exposed (typically using the provider constructor). Flag-keys are transformed appropriately for the underlying backend (OpenFeature defines no restrictions in valid flag keys). Context is transformed appropriately for the underlying backend. Provider is named according to the [naming conventions](https://openfeature.dev/docs/reference/concepts/provider#naming-recommendations) Copy as Markdown #### Non-functional requirements Documentation for installation, all behaviors (options, context transformation, flag-key transformation). Provider is adequately tested. Provider is added to [the documentation site](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&template=document-provider.yaml\&title=%5BProvider%5D%3A+). Provider releases follow [semantic versioning](https://semver.org/). Document the version of the OpenFeature specification to which your provider complies. Copy as Markdown ### FAQ #### Does my provider's version need to match the spec version to which it complies? No. You should document the version of the OpenFeature specification your provider complies to, and otherwise use [semantic versioning](https://semver.org/) in your artifact numbers. #### My backend needs a targeting key (or other user identifier), how can I ensure one exists? Targeting keys are not required by the Evaluation API. If your backend requires one, you have a couple options: * Indicate an error in your provider (this will result in the default value being returned). * Generate one automatically. Be sure to document this behavior. #### How should I handle error conditions? If an error condition is encountered in your provider, an error should be [indicated](https://openfeature.dev/specification/types.md#error-code) by throwing or returning an error, as language idioms dictate. The SDK will ensure the default value is returned and expose the relevant error data. #### Why isn't there an [error code](https://openfeature.dev/specification/types.md#error-code) for disabled flags? The evaluation of disabled flags does not constitute an "exceptional" occurrence. Flag management systems which feature the ability to disable flags typically expect that flags in this state might be evaluated by client applications. For this reason, OpenFeature defines a [disabled reason](https://openfeature.dev/specification/types.md#resolution-details), not a disabled error. #### My flag system doesn't support flags of a particular type. What should I do? If your backend system doesn't support a particular flag type, you can: * Do a conversion from a type the system supports (for example, store JSON objects in a string, or booleans as `0`/`1`), and document this pattern. * Indicate an error in your provider (this will result in the default value being returned). #### How can people find my provider? Please add your provider to the [documentation](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so people can find it! #### Where should I put my provider source code? If you are a vendor and you want to maintain full control over your provider, you should host the provider in your organization's SCM. If you would like your provider to be maintained as a community project, please take a look at our various [contrib repositories](https://github.com/orgs/open-feature/repositories?q=contrib\&type=all\&language=\&sort=). We encourage you to make your code open source, but obviously it's your decision. #### Where should I publish my provider artifact? You should publish your provider on the appropriate package management repository for your language, keeping in mind the [naming conventions](https://openfeature.dev/docs/reference/concepts/provider#naming-recommendations). Our [contrib repositories](https://github.com/orgs/open-feature/repositories?q=contrib\&type=all\&language=\&sort=) publish all their artifacts as part of their release processes. --- # SDK Paradigms OpenFeature SDKs come in two "flavors"; those intended for use in server applications and those intended for use in client applications. The primary difference between these two paradigms has to do with the way they model [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md). ## Dynamic Context Paradigm (Server-side SDKs) In server usage, the evaluation context changes frequently, as often as every evaluation. Though some context data may remain static for the entire application lifecycle (such as the hostname or application version) much of it may be dynamic. For instance, on a web-server, the context might contain the remote IP of the connected client or the session-id or email address of the logged-in user. For this reason, the evaluation API defines a parameter for the evaluation context which can be provided at each evaluation. ## Static Context Paradigms (Client-side SDKs) In client-side usage, the evaluation context changes less frequently, often in response to user actions or UI events. In this case, the context frequently corresponds to a single user, and the context data represents facts about them (such as their name or subscription level) or the application state (if the user has items in their cart). When the context data changes, it's often necessary for the provider to update its cache of evaluated flags, or request an updated ruleset from the server; we refer to this as *context reconciliation*. SDKs using the static-context paradigm define a `context changed handler` for providers to implement which runs any time the context is modified. Client-side implementations include some additional event types: [PROVIDER_RECONCILING](https://openfeature.dev/docs/reference/concepts/events.md#provider_reconciling-static-contextclient-side-only) and [PROVIDER_CONTEXT_CHANGED](https://openfeature.dev/docs/reference/concepts/events.md#provider_context_changed-static-contextclient-side-only). These are emitted when the provider's context is invalidated, and after it's subsequently reconciled, accordingly. --- # Tracking Tracking in OpenFeature is the process of associating metrics or KPIs with feature flag evaluation contexts. This association helps measure the impact of feature changes on both business KPIs and system performance, making it particularly valuable for experimentation. ## Use Cases Tracking events associated with flag evaluations serves two primary purposes: ### Performance Monitoring When changes are made to an application via feature flags, tracking helps measure their impact on performance. By associating events with flag evaluation contexts and sending this data to telemetry or analytics platforms, teams can determine whether specific flag configurations improve or degrade measured performance, whether business metrics or system performance. ### Experimentation Tracking creates a crucial link between flag evaluations and business outcomes, enabling robust experimentation. Experimentation differs from generalized Performance Monitoring in its execution. The most common form of Experimentation being A/B testing which is when two variations of an application or feature are distributed randomly to similar groups and the differences in metrics are evaluated. For example, if a feature flag controls the order of items in a menu, tracking events can be emitted when users click on menu items. The feature flag provider can typically be set to distribute the different variations equally to your audience, making this an A/B test, helping to validate hypotheses about user behavior in a statistically relevant manner. ## Providers, Hooks and Integration In order to accomplish the use cases above, it's required that the flag provider in use reports events or metrics about the flag evaluations and [tracking calls](https://openfeature.dev/specification/sections/providers.md#27-tracking-support) it facilitates. If your vendor or home-grown solution does not support these functionalities, the OpenFeature SDK offers various integration and extension points to help. You can implement your own provider [track](https://openfeature.dev/specification/sections/tracking.md#61-tracking-api) function (by extending or encapsulating your provider) and use custom [hooks](https://openfeature.dev/docs/reference/concepts/hooks.md), to collect and export the appropriate event data to your customer data or analytics platform. ```mermaid sequenceDiagram Evaluation API->>+Tracking Hook: evaluate Tracking Hook->>Analytics Platform: before Tracking Hook->>Analytics Platform: after Tracking Hook->>-Evaluation API: evaluate Evaluation API->>Analytics Platform: track ``` ## Track Event Implementation The `track` function requires only a label parameter. You don’t need to pass an identifier or flag evaluation context since the OpenFeature provider already maintains this information. Optionally, you can associate additional metadata with each track event if your feature flagging tool supports it. **TypeScript** ```ts // example tracking event recording a conversion client.track('conversion-tracked'); // optionally additional data can be associated with the event client.track('conversion-tracked', { value: 99.77, currencyCode: 'USD', }); ``` **Java** ```java // example tracking event recording a conversion client.track("conversion-tracked"); // optionally additional data can be associated with the event client.track("conversion-tracked", new TrackingEventDetails(99.77).add("currencyCode", "USD")); ``` **C#** ```csharp // example tracking event recording a conversion client.Track("visited-promo-page"); // optionally additional data can be associated with the event client.Track("visited-promo-page", new TrackingEventDetailsBuilder().SetValue(99.77).Set("currencyCode", "USD").Build()); ``` **Go** ```go client.Track( context.Background(), "visited-promo-page", openfeature.EvaluationContext{}, openfeature.NewTrackingEventDetails(99.77).Add("currencyCode", "USD"), ) ``` --- # Welcome to OpenFeature OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. ## What are feature flags? In the most basic case, you can think of a feature flag as an if/else statement that can be controlled at runtime. Feature flags allow application behavior to be altered without the deployment of new code. This capability serves a variety of purposes. You can reduce the need for long-running feature branches. You can hide work-in-progress functionality from end users, while still exposing it for internal testing. You can perform canary releases - rolling out a new feature to an initially small subset of users. You can perform A/B testing. You can safely degrade parts of a production system that are experiencing an outage. You can restrict access to specific product functionality based on user-characteristics such as geography or IP address, for usability, compliance or licensing reasons. Feature flags are *dynamic*; they are evaluated at runtime. Many of the use cases just described also require feature flags to be *context-aware* - a flagging decision must take into account things like which user is making a web request. What's more, the configuration behind flagging decisions also needs to be dynamic to support use cases like canary releases where you gradually roll a feature out to more users without having to redeploy or restart anything. Given all these requirements (along with others such as an admin UI, audit trails, environment management, and more) it's clear that full use of feature flags requires a feature flagging system - typically a stand-alone feature-flagging service along with a client library which interacts with that service. ![](/assets/images/ff-service-9bfd5d029bfcd0ebbea6c6cab79b6a14.png) *A typical feature-flagging system* ## What is OpenFeature? OpenFeature provides a shared, standardized feature flagging client - an *SDK* - which can be plugged into various 3rd-party feature flagging *providers*. Whether you're using an open-source system or a commercial product, whether it's self-hosted or cloud-hosted, OpenFeature provides a consistent, unified API for developers to use feature flagging in their applications. ![](/assets/images/of-architecture-a49b167df4037d936bd6623907d84de1.png) *OpenFeature integrated with a hypothetical "Flags-R-us" flag management system* To accomplish this, the OpenFeature SDK defines a number of flexible abstractions. Video: ### Evaluation API The [evaluation API](https://openfeature.dev/docs/reference/concepts/evaluation-api.md) is the part of the OpenFeature SDK that an *application author* interacts with. It allows developers to evaluate feature flags, and use the resulting values to impact control flow or application characteristics. The evaluation API provides a framework that allows for customization of behavior and integration with various tools. ### Evaluation Context The [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) is a container for arbitrary contextual data that can be used as a basis for dynamic evaluation. Static data such as the host or an identifier for the application can be configured globally. Dynamic evaluation context, such as the IP address of the client in a web application, can be implicitly propagated or explicitly passed during flag evaluation, and can be merged with static values. ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are the "translation layer" between the evaluation API and the flag management system in use. Providers are responsible for mapping the arguments supplied to the evaluation API to their equivalent representation in the associated flag management system. Providers might wrap a vendor SDK, call a bespoke flag evaluation REST API, or even parse some locally stored file to resolve flag values. ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) are a mechanism that allow for the addition of arbitrary behavior at various points in the *flag evaluation life-cycle*. Hooks let you extend the OpenFeature SDK, adding functionality such as validating a resolved flag value, modifying or adding data to the evaluation context, logging, telemetry, and tracking. ### Events [Events](https://openfeature.dev/docs/reference/concepts/events.md) enable the ability to react to state changes in the provider or underlying flag management system. These include changes in provider readiness, error status, or perhaps most interestingly, flag configuration changes. --- # OpenFeature CLI [![work-in-progress](https://img.shields.io/badge/status-WIP-yellow)](https://github.com/orgs/open-feature/projects/17)[![Slack](https://img.shields.io/badge/slack-%40cncf%2Fopenfeature-brightgreen?style=flat\&logo=slack)](https://cloud-native.slack.com/archives/C07DY4TUDK6) The OpenFeature CLI is a command-line tool designed to improve the developer experience when working with feature flags. It helps developers manage feature flags consistently across different environments and programming languages by providing powerful utilities for code generation, flag validation, and more. The CLI bridges the gap between feature flag management systems and your application code by generating strongly typed flag accessors from a flag manifest. This approach provides: * **Type Safety**: Generate strongly-typed flag accessors for your preferred language * **Developer Experience**: Reduce errors and improve IDE autocomplete support * **Language Support**: Generate code for TypeScript, JavaScript, React, Go, C#, and more ## Installation ### via curl The OpenFeature CLI can be installed using a shell command. This method is suitable for most Unix-like operating systems. ```bash curl -fsSL https://openfeature.dev/scripts/install_cli.sh | sh ``` ### via Docker The OpenFeature CLI is available as a Docker image in the [GitHub Container Registry](https://github.com/open-feature/cli/pkgs/container/cli). You can run the CLI in a Docker container using the following command: ```bash docker run -it -v $(pwd):/local -w /local ghcr.io/open-feature/cli:latest ``` ### via Go If you have `Go >= 1.25` installed, you can install the CLI using the following command: ```bash go install github.com/open-feature/cli/cmd/openfeature@latest ``` ### via pre-built binaries Download the appropriate pre-built binary from the [releases page](https://github.com/open-feature/cli/releases). ## Quick Start 1. Create a flag manifest file in your project root: ```bash cat > flags.json << EOL { "$schema": "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag-manifest.json", "flags": { "enableMagicButton": { "flagType": "boolean", "defaultValue": false, "description": "Activates a special button that enhances user interaction with magical, intuitive functionalities." } } } EOL ``` Note This is for demonstration purposes only. In a real-world scenario, you would typically want to fetch this file from a remote flag management service. See [here](https://github.com/open-feature/cli/issues/3), more details. 2. Generate code for your preferred language: ```bash openfeature generate react ``` See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_generate.md) for all available options. 3. View the generated code: ```bash cat openfeature.ts ``` **Congratulations!** You have successfully generated your first strongly typed flag accessors. You can now use the generated code in your application to access the feature flags. This is just scratching the surface of what the OpenFeature CLI can do. For more advanced usage, read on! ## Commands The OpenFeature CLI provides the following commands: | Command | Description | | ---------- | ---------------------------------------------- | | `init` | Initialize a new flag manifest | | `manifest` | Manage flag manifest files (add, list, delete) | | `compare` | Compare two flag manifests | | `generate` | Generate strongly typed flag accessors | | `pull` | Fetch flags from remote sources | | `push` | Push flags to remote services | | `version` | Display CLI version | ### `init` Initialize a new flag manifest in your project. ```bash openfeature init ``` This command creates a `flags.json` file in your current directory with the proper schema reference. You can customize the manifest path using configuration options. See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_init.md) for all available options. ### `manifest` Manage flag manifest files with subcommands for adding, listing, and deleting flags. ```bash # Add a new flag interactively openfeature manifest add # Add a boolean flag openfeature manifest add new-feature --default-value false # Add a string flag with description openfeature manifest add welcome-message \ --type string \ --default-value "Hello!" \ --description "Welcome message for users" # List all flags in the manifest openfeature manifest list # Delete a flag from the manifest openfeature manifest delete old-feature ``` The manifest command provides: * **add**: Add new flags to your manifest file * **list**: Display all flags with their configuration * **delete**: Remove flags from your manifest file See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_manifest.md) for all available options. ### `compare` Compare two feature flag manifests and display the differences. ```bash # Compare your local manifest against another openfeature compare --against production-flags.json # Compare with different output formats openfeature compare --against other.json --output json openfeature compare --against other.json --output yaml openfeature compare --against other.json --output flat ``` Output formats: * **tree**: Hierarchical tree view (default) * **flat**: Simple flat list * **json**: JSON format * **yaml**: YAML format See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_compare.md) for all available options. ### `generate` Generate strongly typed flag accessors for your project. ```bash # List available languages openfeature generate # Generate for a specific language openfeature generate typescript # With custom output directory openfeature generate typescript --output ./src/flags ``` **Supported Languages:** | Language | Description | | --------- | ----------------------------- | | `react` | React hooks for feature flags | | `go` | Go flag accessors | | `csharp` | C# flag accessors | | `java` | Java flag accessors | | `python` | Python flag accessors | | `nestjs` | NestJS flag accessors | | `nodejs` | Node.js flag accessors | | `angular` | Angular flag accessors | See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_generate.md) for all available options. > **_NOTE:_** Angular generated code requires `@openfeature/angular-sdk` version `1.1.0` or newer. ### `pull` Fetch feature flag configurations from a remote source. ```bash # Pull flags from a remote API openfeature pull --flag-source-url https://api.example.com # With authentication openfeature pull --flag-source-url https://api.example.com --auth-token secret-token # Pull from a JSON file URL openfeature pull --flag-source-url https://example.com/flags.json ``` The pull command supports: * HTTP/HTTPS endpoints implementing the OpenFeature Manifest Management API * Direct JSON/YAML file URLs * Authentication via bearer tokens See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_pull.md) for all available options. ### `push` Push local flag configurations to a remote flag management service. ```bash # Push flags to a remote API openfeature push --flag-source-url https://api.example.com --auth-token secret-token # Dry run to preview changes openfeature push --flag-source-url https://api.example.com --dry-run ``` The push command intelligently: * Fetches existing flags from the remote * Compares local flags with remote flags * Creates new flags that don't exist remotely * Updates existing flags that have changed See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_push.md) for all available options. ### `version` Print the version number of the OpenFeature CLI. ```bash openfeature version ``` See [here](https://github.com/open-feature/cli/blob/main/docs/commands/openfeature_version.md) for all available options. ## Flag Manifest The flag manifest is a JSON file that defines your feature flags and their properties. It serves as the source of truth for your feature flags and is used by the CLI to generate strongly typed accessors. The manifest file should be named `flags.json` and placed in the root of your project. ### Flag Manifest Structure The flag manifest file should follow the [JSON schema](https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag-manifest.json) with the following properties: * `$schema` - The URL of the JSON schema for validation * `flags` - An object containing the feature flags * `flagKey` - A unique key for the flag * `description` - A description of what the flag does * `type` - The type of the flag (`boolean`, `string`, `number`, `object`) * `defaultValue` - The default value of the flag ### Example Flag Manifest ```json { "$schema": "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag-manifest.json", "flags": { "uniqueFlagKey": { "description": "Description of what this flag does", "type": "boolean|string|number|object", "defaultValue": "default-value", } } } ``` ## Remote Flag Management The OpenFeature CLI supports synchronizing flags with remote flag management services through a standardized OpenAPI-based approach. This enables teams to: * **Pull flags** from centralized flag management systems * **Push flags** back to maintain consistency across environments * **Integrate** with any service that implements the Manifest Management API ### OpenAPI Client Pattern The CLI uses an OpenAPI-driven architecture for remote operations: 1. **Standardized API**: All remote operations conform to the [Manifest Management API](https://github.com/open-feature/cli/blob/main/api/v0/sync.yaml) OpenAPI specification 2. **Type-Safe Clients**: Generated clients provide compile-time safety and better IDE support 3. **Provider Agnostic**: Any service implementing the API specification can integrate with the CLI For detailed information about implementing or extending the OpenAPI client pattern, see the [OpenAPI Client Pattern documentation](https://github.com/open-feature/cli/blob/main/docs/openapi-client-pattern.md). ### Implementing the Manifest Management API If you're building a flag management service that needs to integrate with the OpenFeature CLI, implement the endpoints defined in the [sync.yaml](https://github.com/open-feature/cli/blob/main/api/v0/sync.yaml) specification: * `GET /openfeature/v0/manifest` - Retrieve the project manifest * `POST /openfeature/v0/manifest/flags` - Create new flags * `PUT /openfeature/v0/manifest/flags/{key}` - Update existing flags * `DELETE /openfeature/v0/manifest/flags/{key}` - Archive/delete flags ## Configuration The OpenFeature CLI uses an optional configuration file to override default settings and customize behavior. This file can be in JSON or YAML format and should be named either `.openfeature.json` or `.openfeature.yaml`. ### Configuration File Structure ```yaml # Example .openfeature.yaml manifest: "flags/manifest.json" # Overrides the default manifest path generate: output: "src/flags" # Overrides the default output directory # Any language-specific options can be specified here # For example, for React: react: output: "src/flags/react" # Overrides the default React output directory # For Go: go: package: "github.com/myorg/myrepo/flags" # Overrides the default Go package name output: "src/flags/go" # Overrides the default Go output directory ``` ### Configuration Priority The CLI uses a layered approach to configuration, allowing you to override settings at different levels. The configuration is applied in the following order: ```mermaid flowchart LR default("Default Config") config("Config File") args("Command Line Args") default --> config config --> args ``` --- # OpenFeature MCP Server The **OpenFeature Model Context Protocol (MCP) Server** enables AI coding assistants to interact with OpenFeature through a standardized protocol. It provides SDK installation guidance and feature flag evaluation capabilities directly within your AI-powered development environment. The OpenFeature MCP Server is a local tool that connects AI coding assistants (like **Cursor**, **Claude Code**, **VS Code**, and **Windsurf**) to OpenFeature functionality. It acts as a bridge between your AI assistant and OpenFeature capabilities, enabling intelligent code generation and migration, SDK installation guidance, and feature flag evaluation. This server is published to the [MCP Registry](https://registry.modelcontextprotocol.io) under `dev.openfeature/mcp`. > **⚠️ AI Agent Behavior**: AI agents are non-deterministic and may not complete tasks correctly. Always manually review their changes before committing. If you encounter issues, please [open an issue](https://github.com/open-feature/mcp/issues) with details about your AI agent (e.g., Claude Code + Sonnet 4.5, Cursor + gpt-5-codex) with the commands you used and the behavior you saw. ## Quick Start ### NPX Install The easiest way to use the OpenFeature MCP Server is through NPX, which requires no installation: ```json { "mcpServers": { "OpenFeature": { "command": "npx", "args": ["-y", "@openfeature/mcp"] } } } ``` ### NPM Global Install You can install the MCP server globally: ```bash npm install -g @openfeature/mcp ``` Then configure your AI assistant to use the global installation: ```json { "mcpServers": { "OpenFeature": { "command": "openfeature-mcp" } } } ``` ## AI Assistant Configuration ### Cursor [📦 Install in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=OpenFeature\&config=eyJjb21tYW5kIjogIm5weCIsICJhcmdzIjogWyIteSIsICJAb3BlbmZlYXR1cmUvbWNwIl19Cg==) To open Cursor and automatically add the OpenFeature MCP, click the install button above. Alternatively, navigate to `Cursor Settings` -> `Tools & MCP` -> `New MCP Server` and add to `~/.cursor/mcp_settings.json`: ```json { "mcpServers": { "OpenFeature": { "command": "npx", "args": ["-y", "@openfeature/mcp"] } } } ``` ### VS Code [📦 Install in VS Code](https://vscode.dev/redirect/mcp/install?name=OpenFeature\&config=%7B%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40openfeature%2Fmcp%22%5D%7D) To open VS Code and automatically add the OpenFeature MCP, click the install button above. For more details, see the [VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). Alternatively, add to `.vscode/mcp.json` in your project: ```json { "mcpServers": { "OpenFeature": { "command": "npx", "args": ["-y", "@openfeature/mcp"], "env": { // Optional environment variables "OPENFEATURE_OFREP_BASE_URL": "", "OPENFEATURE_OFREP_API_KEY": "" } } } } ``` ### Claude Code Add the server via the Claude Code CLI: ```bash claude mcp add --transport stdio openfeature npx -y @openfeature/mcp ``` Then manage the connection with `/mcp` in the CLI. ### Windsurf In the `Manage MCP servers` raw config, add: ```json { "mcpServers": { "OpenFeature": { "command": "npx", "args": ["-y", "@openfeature/mcp"] } } } ``` ### Codex CLI Edit `~/.codex/config.toml`: ```toml [mcp_servers.openfeature] command = "npx" args = ["-y", "@openfeature/mcp"] ``` Restart Codex CLI after saving. ### Gemini CLI Edit `~/.gemini/settings.json`: ```json { "mcpServers": { "OpenFeature": { "command": "npx", "args": ["-y", "@openfeature/mcp"] } } } ``` Restart Gemini CLI after saving. ### Claude Desktop Edit your Claude Desktop config at: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` Add the following configuration: ```json { "mcpServers": { "openfeature": { "command": "npx", "args": ["-y", "@openfeature/mcp"] } } } ``` Restart Claude Desktop after saving. ## Available Tools The OpenFeature MCP Server provides two main tools accessible to AI assistants: ### SDK Installation Guide: `install_openfeature_sdk` Fetches installation instructions for OpenFeature SDKs in various languages and frameworks. Optionally includes provider-specific setup documentation. #### SDK Tool Parameters | Parameter | Type | Required | Description | | ------------ | --------- | -------- | --------------------------------------------------------- | | `technology` | string | Yes | Target language/framework (see supported list below) | | `providers` | string\[] | No | Provider identifiers to include installation instructions | #### Supported Technologies The technologies list is built from the available `prompts/*.md`, updated automatically using `scripts/build-prompts.js` | Technology | SDK | | ------------ | ------------------ | | `android` | Android Kotlin SDK | | `dotnet` | .NET SDK | | `go` | Go SDK | | `ios` | iOS Swift SDK | | `java` | Java SDK | | `javascript` | JavaScript Web SDK | | `nestjs` | NestJS SDK | | `nodejs` | Node.js SDK | | `php` | PHP SDK | | `python` | Python SDK | | `react` | React SDK | | `ruby` | Ruby SDK | #### Supported Providers The provider list is automatically sourced from the OpenFeature ecosystem (`open-feature/openfeature.dev` repo). See `scripts/build-providers.js` for details on how the provider list is maintained. ### OFREP Flag Evaluation: `ofrep_flag_eval` Evaluate feature flags using the OpenFeature Remote Evaluation Protocol (OFREP). Supports both single flag and bulk evaluation. #### OFREP Tool Parameters | Parameter | Type | Required | Description | | ------------------- | ------ | -------- | --------------------------------------------------------- | | `base_url` | string | No | Base URL of your OFREP-compatible flag service | | `flag_key` | string | No | Flag key for single evaluation (omit for bulk) | | `context` | object | No | Evaluation context (e.g., `{ targetingKey: "user-123" }`) | | `etag` | string | No | ETag for bulk evaluation caching | | `auth` | object | No | Authentication configuration | | `auth.bearer_token` | string | No | Bearer token for authorization | | `auth.api_key` | string | No | API key for authorization | #### OFREP Configuration To use OFREP flag evaluation features, configure authentication and endpoint details. The server checks configuration in this priority order: 1. **Tool Arguments** * `base_url` * `auth.bearer_token` / `auth.api_key` 2. **Environment Variables** * `OFREP_ENDPOINT` (preferred) or `OPENFEATURE_OFREP_BASE_URL` / `OFREP_BASE_URL` * `OFREP_HEADERS` (comma-separated `key=value` pairs, URL-decoded before parsing) * `OFREP_TIMEOUT_MS` (positive integer milliseconds) * `OPENFEATURE_OFREP_BEARER_TOKEN` or `OFREP_BEARER_TOKEN` * `OPENFEATURE_OFREP_API_KEY` or `OFREP_API_KEY` 3. **Configuration File**: `~/.openfeature-mcp.json` `OFREP_HEADERS` parsing follows the OFREP protocol convention: * URL-decode the full value first * Split by comma into header pairs * Split each pair by the first equals sign (`=`) * Trim whitespace around keys/values * Skip malformed entries Examples: * `OFREP_HEADERS=Authorization=Bearer%20token,X-Custom=value` * `OFREP_TIMEOUT_MS=5000` Example `~/.openfeature-mcp.json`: ```json { "OFREP": { "baseUrl": "https://flags.example.com", "bearerToken": "", "apiKey": "", "headers": { "X-Custom-Header": "value" }, "timeoutMs": 5000 } } ``` You can override the config file path using the `OPENFEATURE_MCP_CONFIG_PATH` environment variable. > **Note**: All logs are written to stderr. The MCP protocol messages use stdout. ## MCP Usage Examples ### SDK Installation Example > "install the OpenFeature SDK for Node.js with the flagd provider" The AI will use the MCP to fetch relevant installation instructions and attempt to install the OpenFeature SDK with the correct provider. ### Flag Evaluation Example When interacting with your AI assistant: > "Can you check the value of the 'new-checkout-flow' feature flag for 'user-123'?" The AI will use the MCP to evaluate the flag using OFREP and provide you with the result, along with additional metadata like variant and reason. --- # OpenFeature Remote Evaluation Protocol (OFREP) The **OpenFeature Remote Evaluation Protocol (OFREP)** is an [API specification](https://openfeature.dev/docs/reference/other-technologies/ofrep/openapi.md) for feature flagging that enables vendor-agnostic communication between applications and flag management systems. It defines a standard API layer between the provider and the flag management system, allowing any open source or commercial system to implement the protocol and be compatible with community-maintained providers. ### Key Benefits * **Vendor Agnostic**: Connect to any OFREP-compliant flag management system without vendor-specific implementations * **Standardized**: Built on a common [OpenAPI specification](https://openfeature.dev/docs/reference/other-technologies/ofrep/openapi.md) for consistent integration * **Flexible**: Works on both client-side and server-side applications * **Community Maintained**: Generic OFREP providers maintained by the OpenFeature project ## How It Works OFREP is **a protocol, not a provider**. It defines how to communicate with feature flag management systems and works on top of OpenFeature SDKs by providing standardized providers. ```mermaid graph LR A[Your Application] --> B[OpenFeature SDK] B --> C[OFREP Provider] C --> D[OFREP API] D --> E[Flag Management System] ``` 1. Your application uses the OpenFeature SDK 2. The OpenFeature SDK uses an OFREP provider 3. The OFREP provider communicates with your flag management system via the standardized OFREP API 4. Your flag management system implements the [OFREP specification](https://openfeature.dev/docs/reference/other-technologies/ofrep/openapi.md) ### Server vs Client OFREP supports both paradigms defined by OpenFeature: #### Server-Side (Dynamic Context) * Evaluation happens by calling the OFREP API with context * API request with evaluation context is made on every evaluation * Context per evaluation #### Client-Side (Static Context) * All flags are evaluated in one OFREP API call using a common static context * Then local flag evaluation happens against the cached static context * Context represents a single user or session ## Using OFREP ### Available Providers The OpenFeature community maintains OFREP providers for multiple languages. View the complete list of [OFREP providers in the ecosystem](https://openfeature.dev/ecosystem/?instant_search%5BrefinementList%5D%5Bvendor%5D%5B0%5D=OFREP). ### Available Flag Management Systems Several flag management systems support OFREP. View the complete list of [OFREP-compliant systems in the ecosystem](https://openfeature.dev/ecosystem/?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=OFREP%20API). ## Implementing OFREP ### For Provider Developers To create an OFREP provider for a new language: 1. Review the [server provider guideline](https://github.com/open-feature/protocol/blob/main/guideline/dynamic-context-provider.md) or [client provider guideline](https://github.com/open-feature/protocol/blob/main/guideline/static-context-provider.md) 2. Implement the provider for your SDK according to the guidelines 3. Register your provider in the [OpenFeature ecosystem](https://github.com/open-feature/openfeature.dev/issues) ### For Flag Management System Developers To make your flag management system OFREP-compliant: 1. Implement the [OFREP OpenAPI specification](https://openfeature.dev/docs/reference/other-technologies/ofrep/openapi.md) 2. Test your implementation with existing OFREP providers 3. Register your system in the [OpenFeature ecosystem](https://github.com/open-feature/openfeature.dev/issues) --- * OFREP Core * postEvaluate A Single Feature Flag * postBulk Evaluate All Feature Flags [API docs by Redocly](https://redocly.com/redoc/) # OpenFeature Remote Evaluation Protocol (OFREP) (0.4.0) Download OpenAPI specification:[Download](https://raw.githubusercontent.com/open-feature/protocol/main/service/openapi.yaml) URL: License: Apache-2.0 *** The **OpenFeature Remote Evaluation Protocol (OFREP)** is an API specification for feature flagging that enables vendor-agnostic communication between applications and flag management systems. OFREP defines a standard API layer between OpenFeature providers and flag management systems, allowing any flag management system to implement the protocol and be compatible with community-maintained providers. For more information, see the [OFREP documentation](https://openfeature.dev/docs/reference/other-technologies/ofrep/). ## [](#tag/OFREP-Core)OFREP Core **Required**: Core APIs to implement to support OFREP.
*This is the minimum set of APIs required for a flag management system to be OFREP compatible.* ## [](#tag/OFREP-Core/operation/evaluateFlag)Evaluate A Single Feature Flag Evaluates a single feature flag by its key. This endpoint is used by **server-side providers** for dynamic context evaluation, where each evaluation request includes the evaluation context. The endpoint returns the evaluated flag value along with metadata including the evaluation reason, variant, and any flag-specific metadata. The flag value can be one of several types: boolean, string, integer, float, object, or a code default (indicating the provider should use the code default value). **Use Case**: Server-side applications where evaluation context may change between requests and real-time targeting decisions are required. ##### Authorizations: *ApiKeyAuth*_BearerAuth_ ##### path Parameters | | | | ----------- | ----------------------------------------------------------------------------- | | keyrequired | stringExample: discount-bannerThe unique identifier (key) of the feature flag | ##### Request Body schema: application/jsonrequired Evaluation request containing the context for flag evaluation | | | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | required | object (context)Evaluation context containing information used to evaluate feature flags. The context may include an optional `targetingKey` identifying the subject of the evaluation, along with additional properties such as user attributes, session data, or request metadata that can be used for targeting rules. | | targetingKey string property name\* additional property any | | ### Responses https\://raw\.githubusercontent.com/ofrep/v1/evaluate/flags/{key} ### Request samples * Payload Content type application/json `{ "context": { "targetingKey": "user-123", "email": "user@example.com", "custom-plan": "premium", "country": "CA" } }` ### Response samples * 200 * 400 * 404 * 500 Content type application/json `{ "key": "discount-banner", "value": true, "reason": "TARGETING_MATCH", "variant": "enabled" }` ## [](#tag/OFREP-Core/operation/evaluateFlagsBulk)Bulk Evaluate All Feature Flags Evaluates all feature flags in a single request using a static context. This endpoint is used by **client-side providers** for static context evaluation, where all flags are evaluated once and then cached locally for subsequent use. The endpoint returns an array of all flag evaluations, where each flag can be either a successful evaluation or an evaluation failure. The response includes an ETag header for cache validation, allowing clients to use the `If-None-Match` header to avoid unnecessary re-evaluation when flags haven't changed. ##### Authorizations: *ApiKeyAuth*_BearerAuth_ ##### query Parameters | | | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | flagConfigEtag | stringExample: flagConfigEtag=550e8400-e29b-41d4-a716-446655440000Optional ETag metadata provided by an event stream for change-triggered re-fetches (see ADR-0008). This is not a standard HTTP conditional request header; it is metadata for server-side cache validation and freshness checks. It should only be included when the request is directly triggered by a received change notification event. | | | integer or stringExamples: - flagConfigLastModified=1771622898 -
- flagConfigLastModified=2026-02-20T21:28:18Z -Optional last-modified metadata provided by an event stream for change-triggered re-fetches (see ADR-0008). Supports Unix timestamp in seconds (recommended) or an ISO 8601 date-time string, and is transported as query metadata rather than `If-Modified-Since`. It should only be included when the request is directly triggered by a received change notification event. | ##### header Parameters | | | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | If-None-Match | stringExample: abc123xyzOptional ETag value from a previous bulk evaluation response. If provided and the ETag matches the current flag set, the server will return a 304 Not Modified response, indicating that flags haven't changed since the last evaluation. | ##### Request Body schema: application/jsonrequired | | | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | required | object (context)Evaluation context containing information used to evaluate feature flags. The context may include an optional `targetingKey` identifying the subject of the evaluation, along with additional properties such as user attributes, session data, or request metadata that can be used for targeting rules. | | targetingKey string property name\* additional property any | | ### Responses https\://raw\.githubusercontent.com/ofrep/v1/evaluate/flags ### Request samples * Payload Content type application/json `{ "context": { "targetingKey": "user-456", "email": "user@example.com", "plan": "free", "country": "CA" } }` ### Response samples * 200 * 400 * 500 Content type application/json `{ "flags": [ { "key": "discount-banner", "value": true, "reason": "TARGETING_MATCH", "variant": "enabled" }, { "key": "theme-color", "value": "blue", "reason": "STATIC", "variant": "default" }, { "key": "non-existent-flag", "errorCode": "FLAG_NOT_FOUND", "errorDetails": "Flag 'non-existent-flag' was not found" } ], "eventStreams": [ { "type": "sse", "url": "https://sse.example.com/event-stream?channels=env_abc123_v1", "inactivityDelaySec": 120 } ], "metadata": { "version": "v12" } }` --- # SDK Technologies OpenFeature SDKs and components for a range of supported languages and runtimes. ## Languages and runtimes ### Server # [.NET SDK](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) ## [The official OpenFeature SDK for .NET](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) # [C++ SDK](https://openfeature.dev/docs/reference/sdks/server/cpp.md) ## [The official OpenFeature SDK for C++](https://openfeature.dev/docs/reference/sdks/server/cpp.md) # [Dart SDK](https://openfeature.dev/docs/reference/sdks/server/dart.md) ## [The official OpenFeature SDK for Dart](https://openfeature.dev/docs/reference/sdks/server/dart.md) # [Go SDK](https://openfeature.dev/docs/reference/sdks/server/go.md) ## [The official OpenFeature SDK for Go](https://openfeature.dev/docs/reference/sdks/server/go.md) # [Java SDK](https://openfeature.dev/docs/reference/sdks/server/java.md) ## [The official OpenFeature SDK for Java](https://openfeature.dev/docs/reference/sdks/server/java.md) # [NestJS SDK](https://openfeature.dev/docs/reference/sdks/server/javascript/nestjs.md) ## [The official OpenFeature SDK for NestJS](https://openfeature.dev/docs/reference/sdks/server/javascript/nestjs.md) # [Node.js SDK](https://openfeature.dev/docs/reference/sdks/server/javascript.md) ## [The official OpenFeature SDK for Node.js](https://openfeature.dev/docs/reference/sdks/server/javascript.md) # [PHP SDK](https://openfeature.dev/docs/reference/sdks/server/php.md) ## [The official OpenFeature SDK for PHP](https://openfeature.dev/docs/reference/sdks/server/php.md) # [Python SDK](https://openfeature.dev/docs/reference/sdks/server/python.md) ## [The official OpenFeature SDK for Python](https://openfeature.dev/docs/reference/sdks/server/python.md) # [Ruby SDK](https://openfeature.dev/docs/reference/sdks/server/ruby.md) ## [The official OpenFeature SDK for Ruby](https://openfeature.dev/docs/reference/sdks/server/ruby.md) # [Rust SDK](https://openfeature.dev/docs/reference/sdks/server/rust.md) ## [The official OpenFeature SDK for Rust](https://openfeature.dev/docs/reference/sdks/server/rust.md) ### Client # [Angular SDK](https://openfeature.dev/docs/reference/sdks/client/web/angular.md) ## [The official OpenFeature SDK for Angular](https://openfeature.dev/docs/reference/sdks/client/web/angular.md) # [Dart SDK](https://openfeature.dev/docs/reference/sdks/client/dart.md) ## [OpenFeature static-context Dart client SDK for Dart and Flutter applications (beta).](https://openfeature.dev/docs/reference/sdks/client/dart.md) [🔗](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) # [Flags SDK](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) ## [Vercel Flags SDK with OpenFeature Adapter](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) [🔗](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) # [Flags SDK](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) ## [Vercel Flags SDK with OpenFeature Adapter](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) # [iOS SDK](https://openfeature.dev/docs/reference/sdks/client/swift.md) ## [The official OpenFeature SDK for iOS](https://openfeature.dev/docs/reference/sdks/client/swift.md) # [Kotlin SDK](https://openfeature.dev/docs/reference/sdks/client/kotlin.md) ## [The official OpenFeature SDK for Kotlin targeting Android and other platforms.](https://openfeature.dev/docs/reference/sdks/client/kotlin.md) # [React SDK](https://openfeature.dev/docs/reference/sdks/client/web/react.md) ## [The official OpenFeature SDK for React](https://openfeature.dev/docs/reference/sdks/client/web/react.md) # [Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md) ## [The official OpenFeature SDK for Web](https://openfeature.dev/docs/reference/sdks/client/web.md) --- # Client OpenFeature SDKs and components for client-side usage. ## Languages, frameworks, and runtimes # [Angular SDK](https://openfeature.dev/docs/reference/sdks/client/web/angular.md) ## [The official OpenFeature SDK for Angular](https://openfeature.dev/docs/reference/sdks/client/web/angular.md) # [Dart SDK](https://openfeature.dev/docs/reference/sdks/client/dart.md) ## [OpenFeature static-context Dart client SDK for Dart and Flutter applications (beta).](https://openfeature.dev/docs/reference/sdks/client/dart.md) [🔗](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) # [Flags SDK](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) ## [Vercel Flags SDK with OpenFeature Adapter](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) [🔗](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) # [Flags SDK](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) ## [Vercel Flags SDK with OpenFeature Adapter](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) # [iOS SDK](https://openfeature.dev/docs/reference/sdks/client/swift.md) ## [The official OpenFeature SDK for iOS](https://openfeature.dev/docs/reference/sdks/client/swift.md) # [Kotlin SDK](https://openfeature.dev/docs/reference/sdks/client/kotlin.md) ## [The official OpenFeature SDK for Kotlin targeting Android and other platforms.](https://openfeature.dev/docs/reference/sdks/client/kotlin.md) # [React SDK](https://openfeature.dev/docs/reference/sdks/client/web/react.md) ## [The official OpenFeature SDK for React](https://openfeature.dev/docs/reference/sdks/client/web/react.md) # [Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md) ## [The official OpenFeature SDK for Web](https://openfeature.dev/docs/reference/sdks/client/web.md) --- # OpenFeature Dart Client SDK This package is the vendor-neutral OpenFeature SDK for Dart client applications. It uses the static-context paradigm. It has no Flutter or `dart:io` dependency. This package supports Dart 3.10 or later within Dart 3.x. Provider packages can require a higher Dart version; check the chosen provider separately. Current package version: `0.0.1`. **Release status:** `0.0.1` is the first non-beta client release. The SDK remains pre-1.0. This package provides synchronous typed evaluation, event handlers, hooks, ordered context changes and an in-memory provider. Pre-1.0 versions do not establish a 1.0 API compatibility commitment. Consult the conformance matrix and release notes for accepted support scope. ## Install The client SDK requires Dart 3.10 or later within Dart 3.x. Flutter applications need a Flutter release that includes a compatible Dart SDK. ```yaml dependencies: openfeature_dart_client_sdk: ^0.0.1 # x-release-please-version ``` Run `dart pub get`, or `flutter pub get` in a Flutter application. ## Use the client ```dart import 'package:openfeature_dart_client_sdk/openfeature_dart_client_sdk.dart'; Future main() async { final provider = InMemoryProvider({ 'new-checkout': true, 'welcome-message': 'Hello', }); await OpenFeatureAPI.instance.setProviderAndWait(provider); await OpenFeatureAPI.instance.setEvaluationContextAndWait( EvaluationContext(targetingKey: 'user-123'), ); final client = OpenFeatureAPI.instance.getClient('checkout'); final enabled = client.getBooleanValue('new-checkout', false); final message = client.getStringValue('welcome-message', 'Welcome'); print('$enabled: $message'); await OpenFeatureAPI.instance.shutdown(); } ``` ## Implement a provider Implement `FeatureProvider` with synchronous resolvers. A resolver must use local state. It must not perform network, file, or platform-channel I/O. Implement only the optional capabilities that the provider needs: * `InitializableProvider` for asynchronous initialization. * `ContextReconciliationProvider` for static-context changes. * `ShutdownProvider` for resource cleanup. * `ProviderEventSource` for provider lifecycle events. * `DomainScopedProvider` when one provider instance supports one domain. * `TrackingProvider` for non-blocking tracking. An initializable or reconciling provider must also implement `ProviderEventSource`. It must emit the terminal lifecycle event before its method terminates. The SDK bounds lifecycle waits to 30 seconds by default so a provider contract violation cannot indefinitely block context changes or shutdown. Tests and isolated integrations may select a shorter timeout through `createIsolatedOpenFeatureAPI(lifecycleTimeout: ...)`. When initialization or context reconciliation times out, the SDK quarantines and detaches that provider instance. Its underlying asynchronous work cannot be cancelled safely, so applications must register a new provider instance rather than retrying the timed-out one. Subscription cancellation and shutdown cleanup are bounded by the same timeout. A provider that implements `ContextReconciliationProvider` can have only one active API/domain binding. Use a separate provider instance for each static context. Providers that resolve entirely from the context passed to each resolver and do not reconcile cached state may be shared across domains. The server and client packages share the [`open-feature/dart-sdk`](https://github.com/open-feature/dart-sdk) repository. The repository rename preserves this package name, Dart imports, and independent client releases. ## Scope This package does not include an HTTP transport, an OFREP provider, a vendor provider, persistent storage, or Flutter APIs. ## Validate the package archive Run the repository staging command to create and validate the same isolated client archive used by the publication workflow: ```text dart tool/stage_client_package.dart --dry-run ``` See the [client release procedure](https://github.com/open-feature/dart-sdk/blob/main/doc/client-sdk-release.md) for non-beta release review and historical first-publication instructions. --- # OpenFeature Kotlin SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.9.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/kotlin-sdk/releases/tag/v0.9.0)
![Status](https://img.shields.io/badge/lifecycle-alpha-a0c3d2.svg)[![MavenCentral](https://img.shields.io/maven-central/v/dev.openfeature/android-sdk)](https://mvnrepository.com/artifact/dev.openfeature/android-sdk) ## Quick start ### Requirements The following [Kotlin Multiplatform Targets](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-dsl-reference.html#targets) are supported: | Supported | Platform | Supported versions | | --------- | -------------------- | ------------------------------------------------------------------------------ | | ✅ | Android | SDK 21+ | | ✅ | JVM | JDK 11+ | | ✅ | Native | Linux x64, iosSimulatorArm64, iosArm64 | | ❌ | Native | [Other native targets](https://kotlinlang.org/docs/native-target-support.html) | | ✅ | Javascript (Node.js) | | | ✅ | Javascript (Browser) | | | ❌ | Wasm | | Note that this library adheres to the [Static Context Paradigm](https://openfeature.dev/docs/reference/concepts/sdk-paradigms.md), so it is intended to be used on the **client side** (i.e. mobile apps, web apps and desktop apps), and has not been evaluated for use in other types of applications (e.g. server applications). ### Installation Installation is preferred via Maven Central. #### In Android projects Important Before version 0.6.0 the Maven artifact's id was `android-sdk`. When upgrading to 0.6.0 or higher please do make sure to use the new artifact id, which is `kotlin-sdk`. ```kotlin dependencies { api("dev.openfeature:kotlin-sdk:0.9.0") } ``` #### In multiplatform projects ```kotlin kotlin { sourceSets { commonMain.dependencies { api("dev.openfeature:kotlin-sdk:0.9.0") } } } ``` ### Usage Note In version 0.6.0 the base package name has changed from `dev.openfeature.sdk` to `dev.openfeature.kotlin.sdk`. When upgrading to 0.6.0 or higher please update your imports accordingly. **Example:** `import dev.openfeature.sdk.EvaluationContext` -> `import dev.openfeature.kotlin.sdk.EvaluationContext`. ```kotlin coroutineScope.launch(Dispatchers.Default) { // configure a provider, wait for it to complete its initialization tasks OpenFeatureAPI.setProviderAndWait(customProvider) val client = OpenFeatureAPI.getClient() // get a bool flag value client.getBooleanValue("boolFlag", defaultValue = false) } ``` ## Features | Status | Features | Description | | ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ⚠️ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | | ✅ | [Multi-Provider](#multi-provider) | Combine multiple providers with configurable evaluation strategies. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=kotlin) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```kotlin coroutineScope.launch(Dispatchers.Default) { OpenFeatureAPI.setProviderAndWait(MyProvider()) } ``` Asynchronous API that doesn't wait is also available. It's useful when you want to set a provider and continue with other tasks. However, flag evaluations are only possible after the provider is Ready. ```kotlin OpenFeatureAPI.setProvider(MyProvider()) // can pass a dispatcher here // The provider initialization happens on a coroutine launched on the IO dispatcher. val status = OpenFeatureAPI.getStatus() // When status is Ready, flag evaluations can be made ``` ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```kotlin // set a value to the global context val evaluationContext = ImmutableContext( targetingKey = session.getId, attributes = mutableMapOf("region" to Value.String("us-east-1"))) OpenFeatureAPI.setEvaluationContext(evaluationContext) ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=kotlin) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```kotlin // add a hook globally, to run on all evaluations OpenFeatureAPI.addHooks(listOf(ExampleHook())) // add a hook on this client, to run on all evaluations made by this client val client = OpenFeatureAPI.getClient() client.addHooks(listOf(ExampleHook())) // add a hook for this evaluation only val retval = client.getBooleanValue(flagKey, false, FlagEvaluationOptions(listOf(ExampleHook()))) ``` ### Tracking The [tracking API](https://openfeature.dev/specification/sections/tracking.md) allows you to use OpenFeature abstractions to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. Note that, unlike methods that handle feature flag evaluations, calling `track(...)` may throw an `IllegalArgumentException` if an empty string is passed as the `trackingEventName`. Below is an example of how we can track a "Checkout" event with some `TrackingDetails`. ```kotlin OpenFeatureAPI.getClient().track( "Checkout", TrackingEventDetails( 499.99, ImmutableStructure( "numberOfItems" to Value.Integer(4), "timeInCheckout" to Value.String("PT3M20S") ) ) ) ``` Tracking is optionally implemented by Providers. ### Logging The SDK ships a `Logger` interface and a built-in `LoggingHook` that emits structured log records at each stage of the flag evaluation life-cycle. #### Logger interface ```kotlin interface Logger { fun debug(message: () -> String, attributes: () -> Map = { emptyMap() }, throwable: Throwable? = null) fun info(message: () -> String, attributes: () -> Map = { emptyMap() }, throwable: Throwable? = null) fun warn(message: () -> String, attributes: () -> Map = { emptyMap() }, throwable: Throwable? = null) fun error(message: () -> String, attributes: () -> Map = { emptyMap() }, throwable: Throwable? = null) } ``` Both `message` and `attributes` are lambdas — they are evaluated lazily and only invoked if the logger decides to emit the record. This means inactive log levels incur no allocation overhead. Platform-specific loggers are created via `LoggerFactory.getLogger(tag)`: | Platform | Backend | Format | | ------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android | `android.util.Log` | Logcat with tag; attributes appended as `key=value` pairs | | JVM | `System.out` (DEBUG/INFO) / `System.err` (WARN/ERROR) | ` [LEVEL] - key=value …` | | iOS | `NSLog` | `[LEVEL] - key=value …` (NSLog adds its own timestamp) | | JavaScript | `console` API | `[] ` with attributes as an expandable JS object (browser devtools / Node.js); note: `debug` uses `console.log`, not `console.debug` — browser "Verbose" filter will not capture it | | Linux/Native | `stdout` (DEBUG/INFO) / `stderr` (WARN/ERROR) | `[LEVEL] - key=value …` (no timestamp; systemd/journald provides its own) | #### Custom Logger To route SDK logs into your own logging framework, implement `Logger` and pass it wherever a `Logger` is accepted: ```kotlin // SLF4J's {} placeholder calls toString() on the map, producing {key=value, ...}. // Use entries.joinToString(" ") { "${it.key}=${it.value}" } to get key=value output instead. class MyLogger(private val underlying: org.slf4j.Logger) : Logger { override fun debug(message: () -> String, attributes: () -> Map, throwable: Throwable?) { if (!underlying.isDebugEnabled) return underlying.debug("{} {}", message(), attributes(), throwable) } override fun info(message: () -> String, attributes: () -> Map, throwable: Throwable?) { if (!underlying.isInfoEnabled) return underlying.info("{} {}", message(), attributes(), throwable) } override fun warn(message: () -> String, attributes: () -> Map, throwable: Throwable?) { if (!underlying.isWarnEnabled) return underlying.warn("{} {}", message(), attributes(), throwable) } override fun error(message: () -> String, attributes: () -> Map, throwable: Throwable?) { if (!underlying.isErrorEnabled) return underlying.error("{} {}", message(), attributes(), throwable) } } ``` #### LoggingHook `LoggingHook` logs at each stage of flag evaluation. Register it like any other hook: ```kotlin // dev.openfeature.kotlin.sdk.logging.LoggerFactory — the SDK's platform-specific factory val logger = LoggerFactory.getLogger("FeatureFlags") val hook = LoggingHook( logger = logger, logEvaluationContext = false, // set true to include context attributes in logs beforeLogLevel = LogLevel.DEBUG, afterLogLevel = LogLevel.DEBUG, errorLogLevel = LogLevel.ERROR, finallyLogLevel = LogLevel.DEBUG, ) // register globally OpenFeatureAPI.addHooks(listOf(hook)) ``` #### PII filtering When `logEvaluationContext = true`, context attributes are included in log records. By default, attributes matching common PII field names (see `LoggingHook.DEFAULT_SENSITIVE_KEYS`) are excluded. Two optional parameters let you customize this behaviour: | Parameter | Type | Default | Description | | ------------------- | -------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `logTargetingKey` | `Boolean` | `true` | Include the targeting key in context logs. Set to `false` if targeting keys contain PII such as user IDs or email addresses. | | `includeAttributes` | `Set?` | `null` | Allowlist: only attributes with these names are logged. An empty set logs no attributes. Takes precedence over `excludeAttributes`. Attribute name matching is case-sensitive. | | `excludeAttributes` | `Set` | `DEFAULT_SENSITIVE_KEYS` | Denylist: attributes with these names are omitted. Attribute name matching is case-sensitive. | `LoggingHook.DEFAULT_SENSITIVE_KEYS` contains: `email`, `phone`, `phoneNumber`, `ssn`, `socialSecurityNumber`, `creditCard`, `creditCardNumber`, `password`, `address`, `streetAddress`, `zipCode`, `postalCode`, `ipAddress`, `firstName`, `lastName`, `fullName`, `dateOfBirth`. ```kotlin // Log only a specific set of safe attributes val hook = LoggingHook( logger = logger, logEvaluationContext = true, logTargetingKey = false, // targeting key contains a user ID includeAttributes = setOf("region", "plan", "tier"), // allowlist — only these are logged ) // Or extend the default denylist val hook = LoggingHook( logger = logger, logEvaluationContext = true, excludeAttributes = LoggingHook.DEFAULT_SENSITIVE_KEYS + setOf("internalId", "accountNumber"), ) ``` The hook logs the following at each lifecycle stage: | Stage | Message | Attributes | | -------------- | --------------------------- | -------------------------------------------------------------------------------------------- | | `before` | `Flag evaluation starting` | `flag`, `type`, `defaultValue`, `provider`, `client` (if set), `context.*` (if enabled) | | `after` | `Flag evaluation completed` | `flag`, `value`, `variant` (if set), `reason` (if set), `provider`, `context.*` (if enabled) | | `error` | `Flag evaluation error` | `flag`, `type`, `defaultValue`, `provider`, `error` (if set), `context.*` (if enabled) | | `finallyAfter` | `Flag evaluation finalized` | `flag`, `errorCode` (if set), `errorMessage` (if set) | Example JVM output for a successful evaluation: ```text 2026-04-15T10:00:00.123Z [DEBUG] FeatureFlags - Flag evaluation starting flag=my-flag type=BOOLEAN defaultValue=false provider=MyProvider 2026-04-15T10:00:00.124Z [DEBUG] FeatureFlags - Flag evaluation completed flag=my-flag value=true variant=on reason=TARGETING_MATCH provider=MyProvider 2026-04-15T10:00:00.124Z [DEBUG] FeatureFlags - Flag evaluation finalized flag=my-flag ``` To include evaluation context in logs for a single call, pass a hook hint: ```kotlin client.getBooleanValue( "my-flag", false, FlagEvaluationOptions( hooks = listOf(hook), hookHints = mapOf(LoggingHook.HINT_LOG_EVALUATION_CONTEXT to true) ) ) ``` ### Domains [Domains](https://openfeature.dev/specification/glossary.md#domain) allow you to logically bind clients with providers, enabling multiple provider configurations within the same application. Support for domains is currently in development. ### Eventing Events from the Provider allow the SDK to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Events are optional which mean that not all Providers will emit them and it is not a must have. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. Example usage: ```kotlin viewModelScope.launch { OpenFeatureAPI.observe().collect { println(">> Provider event received") } } viewModelScope.launch { OpenFeatureAPI.setProviderAndWait( MyFeatureProvider(), myEvaluationContext, Dispatchers.Default ) } ``` ### Multi-Provider The Multi-Provider allows you to use multiple underlying providers as sources of flag data for the OpenFeature Kotlin SDK. When a flag is being evaluated, the Multi-Provider will use each underlying provider in order to determine the final result. Different evaluation strategies can be defined to control which providers get evaluated and which result is used. The Multi-Provider is a useful tool for performing migrations between flag providers, or combining multiple providers into a single feature flagging interface. See the [Multi-Provider documentation](https://github.com/open-feature/kotlin-sdk/blob/main/docs/multiprovider/README.md) for more details. ### Shutdown The OpenFeature API provides a close function to perform a cleanup of the registered provider. This should only be called when your application is in the process of shutting down. ```kotlin coroutineScope.launch { OpenFeatureAPI.shutdown() } ``` ## Sample app In the repo there is also a sample app currently under development. The sample app can be used to try out development of a [Provider](#develop-a-provider), a [Hook](#develop-a-hook) or to validate changes to the SDK itself. The sample app should not be used as a reference implementation of how to use the OpenFeature SDK in an Android app. ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. ```kotlin class NewProvider(override val hooks: List>, override val metadata: ProviderMetadata) : FeatureProvider { override fun getBooleanEvaluation( key: String, defaultValue: Boolean, context: EvaluationContext? ): ProviderEvaluation { // resolve a boolean flag value } override fun getDoubleEvaluation( key: String, defaultValue: Double, context: EvaluationContext? ): ProviderEvaluation { // resolve a double flag value } override fun getIntegerEvaluation( key: String, defaultValue: Int, context: EvaluationContext? ): ProviderEvaluation { // resolve an integer flag value } override fun getLongEvaluation( key: String, defaultValue: Long, context: EvaluationContext? ): ProviderEvaluation { // resolve a long (64-bit integer) flag value } override fun getObjectEvaluation( key: String, defaultValue: Value, context: EvaluationContext? ): ProviderEvaluation { // resolve an object flag value } override fun getStringEvaluation( key: String, defaultValue: String, context: EvaluationContext? ): ProviderEvaluation { // resolve a string flag value } override suspend fun initialize(initialContext: EvaluationContext?) { // add context-aware provider initialization } override suspend fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext) { // add necessary changes on context change } override fun track( trackingEventName: String, context: EvaluationContext?, details: TrackingEventDetails? ) { // Optionally track an event } override fun observe(): Flow { // Optionally return a `Flow` of OpenFeatureProviderEvents } } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. Implement your own hook by conforming to the `Hook` interface exported by the OpenFeature SDK. > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature iOS SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.7.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/swift-sdk/releases/tag/0.7.0)
![Status](https://img.shields.io/badge/lifecycle-alpha-a0c3d2.svg) ## Quick start ### Requirements This SDK supports the following Apple platforms: * **iOS 15+** * **macOS 12+** * **watchOS 8+** * **tvOS 15+** The SDK is built with Swift 5.5+ and uses only Foundation and Combine, making it suitable for all Apple platform contexts including mobile, desktop, wearable, and TV applications. It has no third-party dependencies. An optional `OpenFeatureSwiftLog` product integrates with [swift-log](https://github.com/apple/swift-log) for those who use it (see [Logging](#logging)). ### Install #### Xcode Dependencies You have two options, both start from File > Add Packages... in the code menu. First, ensure you have your GitHub account added as an option (+ > Add Source Control Account...). You will need to create a [Personal Access Token](https://github.com/settings/tokens) with the permissions defined in the Xcode interface. 1. Add as a remote repository * Search for `git@github.com:open-feature/swift-sdk.git` and click "Add Package" 2. Clone the repository locally * Clone locally using your preferred method * Use the "Add Local..." button to select the local folder **Note:** Option 2 is only recommended if you are making changes to the client SDK. #### Swift Package Manager If you manage dependencies through SPM, in the dependencies section of Package.swift add: ```swift .package(url: "git@github.com:open-feature/swift-sdk.git", from: "0.7.0") ``` and in the target dependencies section add: ```swift .product(name: "OpenFeature", package: "swift-sdk"), ``` To log through [swift-log](https://github.com/apple/swift-log), also add the optional bridge product: ```swift .product(name: "OpenFeatureSwiftLog", package: "swift-sdk"), ``` ### iOS Usage ```swift import OpenFeature Task { let provider = CustomProvider() // configure a provider, wait for it to complete its initialization tasks await OpenFeatureAPI.shared.setProviderAndWait(provider: provider) // get a bool flag value let client = OpenFeatureAPI.shared.getClient() let flagValue = client.getBooleanValue(key: "boolFlag", defaultValue: false) } ``` ### Privacy Manifest The SDK ships a [privacy manifest](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files) (`PrivacyInfo.xcprivacy`) that Swift Package Manager bundles automatically, so it is picked up by Xcode's privacy report and App Store submission checks. The manifest declares that the SDK: * does not track users (`NSPrivacyTracking` is `false`) and contacts no tracking domains, * does not collect any data types on its own, * does not call any [required reason APIs](https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api). The SDK is an abstraction layer, it holds the evaluation context and tracking events your app supplies and hands them to the configured provider, but never persists or transmits them itself. Any data collection, tracking or required-reason API usage happens in the provider (or hook) you install. Third-party providers and hooks are responsible for declaring that in their own privacy manifest, so check the manifest of each one you use. Your app's own manifest must cover data collection, tracking, and required-reason API use by your app's code, including any custom providers or hooks you write yourself. ## Features | Status | Features | Description | | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ✅ | [Logging](#logging) | Integrate with popular logging packages or your own logger. | | ❌ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [MultiProvider](#multiprovider) | Combine multiple providers with configurable evaluation strategies. | | ✅ | [InMemoryProvider](#inmemoryprovider) | Resolve flags from an in-memory configuration, for demos and testing. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ❌ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Swift) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```swift await OpenFeatureAPI.shared.setProviderAndWait(provider: MyProvider()) ``` > Asynchronous API that doesn't wait is also available ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```swift // Configure your evaluation context and pass it to OpenFeatureAPI let ctx = ImmutableContext( targetingKey: userId, structure: ImmutableStructure(attributes: ["product": Value.string(productId)])) OpenFeatureAPI.shared.setEvaluationContext(evaluationContext: ctx) ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Swift) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```swift // add a hook globally, to run on all evaluations OpenFeatureAPI.shared.addHooks(hooks: ExampleHook()) // add a hook on this client, to run on all evaluations made by this client let client = OpenFeatureAPI.shared.getClient() client.addHooks(ExampleHook()) // add a hook for this evaluation only _ = client.getValue( key: "key", defaultValue: false, options: FlagEvaluationOptions(hooks: [ExampleHook()])) ``` ### Tracking The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. ```swift let client = OpenFeatureAPI.shared.getClient() // Track an event (uses the stored evaluation context on the client) client.track(key: "test") // Track an event with a numeric value client.track(key: "test-value", details: ImmutableTrackingEventDetails(value: 5)) ``` Set evaluation context via provider initialization or `OpenFeatureAPI.shared.setEvaluationContext(...)` before tracking. This client SDK follows the OpenFeature static-context tracking API and does not accept evaluation context at track invocation time. Note that some providers may not support tracking; check the documentation for your provider for more information. ### Logging The SDK does not depend on any logging framework. It logs through the small `OpenFeatureLogger` protocol, so you can plug in whatever logger your app already uses: ```swift public protocol OpenFeatureLogger { func debug(_ message: @autoclosure () -> String) func info(_ message: @autoclosure () -> String) func warning(_ message: @autoclosure () -> String) func error(_ message: @autoclosure () -> String) } ``` Messages are autoclosures, so string interpolation only runs if your implementation emits the message. Implementations may be called from any thread and must be thread-safe. #### Bring your own logger Conform your logger to `OpenFeatureLogger`. For example, with Apple's unified logging: ```swift import OpenFeature import os struct OSLogOpenFeatureLogger: OpenFeatureLogger { private let logger = os.Logger(subsystem: "com.example.app", category: "openfeature") func debug(_ message: @autoclosure () -> String) { logger.debug("\(message())") } func info(_ message: @autoclosure () -> String) { logger.info("\(message())") } func warning(_ message: @autoclosure () -> String) { logger.warning("\(message())") } func error(_ message: @autoclosure () -> String) { logger.error("\(message())") } } ``` #### Using swift-log If you use [swift-log](https://github.com/apple/swift-log), the optional `OpenFeatureSwiftLog` product ships a ready-made `SwiftLogLogger` that forwards to a swift-log `Logger`: ```swift import Logging import OpenFeature import OpenFeatureSwiftLog let logger = SwiftLogLogger(Logger(label: "com.example.app.openfeature")) // or simply: SwiftLogLogger(label: "com.example.app.openfeature") OpenFeatureAPI.shared.setLogger(logger) ``` The core `OpenFeature` product never links swift-log; only apps that add `OpenFeatureSwiftLog` do. #### Configure Logger You can configure logging at three levels, with each level taking precedence over the previous: **1. Global (API-level)** - affects all flag evaluations: ```swift OpenFeatureAPI.shared.setLogger(OSLogOpenFeatureLogger()) ``` **2. Client-level** - affects all evaluations from a specific client: ```swift let client = OpenFeatureAPI.shared.getClient() client.setLogger(OSLogOpenFeatureLogger()) ``` **3. Evaluation-level** - affects a single flag evaluation: ```swift let options = FlagEvaluationOptions(logger: OSLogOpenFeatureLogger()) let value = client.getBooleanValue(key: "my-flag", defaultValue: false, options: options) ``` #### Provider Support Providers can optionally use the logger for debugging and diagnostics. The resolved `OpenFeatureLogger` is passed to providers during flag evaluation through the `logger:` parameter of the evaluation methods, allowing them to log relevant information without depending on any logging framework. If no logger is configured, logging is disabled. The logger is completely optional for both SDK users and provider authors. ##### Migrating providers from swift-log (0.6.x) In 0.6.x the logger-aware `FeatureProvider` methods took a swift-log `Logger?`. They now take `(any OpenFeatureLogger)?`. A provider that still declares the old signature will compile, because the method simply stops matching the protocol requirement, but the SDK will call the default implementation instead and **the provider's logging silently stops**. Update all five logger-aware methods: ```swift // Before (0.6.x) func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?, logger: Logger?) throws -> ProviderEvaluation // After func getBooleanEvaluation( key: String, defaultValue: Bool, context: EvaluationContext?, logger: (any OpenFeatureLogger)? ) throws -> ProviderEvaluation ``` Apply the same change to `getStringEvaluation`, `getIntegerEvaluation`, `getDoubleEvaluation` and `getObjectEvaluation`. Inside the method, `logger?.debug(...)`, `logger?.info(...)`, `logger?.warning(...)` and `logger?.error(...)` calls that pass only a message keep working unchanged. `OpenFeatureLogger` takes just a message, so swift-log-specific arguments such as `metadata:` or `source:` no longer compile; fold that information into the message, and replace `trace`, `notice` or `critical` calls with the nearest of the four levels. Providers that only implement the plain evaluation methods (without `logger:`) need no changes. Drop the `import Logging` if the provider no longer uses swift-log directly. ### Domains Domains allow you to logically bind clients with providers, enabling the use of multiple providers within a single application. Each domain can have its own provider, and clients can be associated with a specific domain. Support for domains is not yet available in the iOS SDK. ### MultiProvider The `MultiProvider` allows you to combine multiple feature flag providers into a single provider, enabling you to use different providers for different flags or implement fallback mechanisms. This is useful when migrating between providers, implementing A/B testing across providers, or ensuring high availability. #### Basic Usage ```swift import OpenFeature Task { // Create individual providers let primaryProvider = PrimaryProvider() let fallbackProvider = FallbackProvider() // Create a MultiProvider with default FirstMatchStrategy let multiProvider = MultiProvider(providers: [primaryProvider, fallbackProvider]) // Set the MultiProvider as the global provider await OpenFeatureAPI.shared.setProviderAndWait(provider: multiProvider) // Use flags normally - the MultiProvider will handle provider selection let client = OpenFeatureAPI.shared.getClient() let flagValue = client.getBooleanValue(key: "my-flag", defaultValue: false) } ``` #### Evaluation Strategies The `MultiProvider` supports different strategies for evaluating flags across multiple providers: ##### FirstMatchStrategy (Default) The `FirstMatchStrategy` evaluates providers in order and returns the first result that doesn't indicate "flag not found". If a provider returns an error other than "flag not found", that error is returned immediately. ```swift let multiProvider = MultiProvider( providers: [primaryProvider, fallbackProvider], strategy: FirstMatchStrategy() ) ``` ##### FirstSuccessfulStrategy The `FirstSuccessfulStrategy` evaluates providers in order and returns the first successful result (no error). Unlike `FirstMatchStrategy`, it continues to the next provider if any error occurs, including "flag not found". ```swift let multiProvider = MultiProvider( providers: [primaryProvider, fallbackProvider], strategy: FirstSuccessfulStrategy() ) ``` #### Use Cases **Provider Migration:** ```swift // Gradually migrate from OldProvider to NewProvider let multiProvider = MultiProvider(providers: [ NewProvider(), // Check new provider first OldProvider() // Fall back to old provider ]) ``` **High Availability:** ```swift // Use multiple providers for redundancy let multiProvider = MultiProvider(providers: [ RemoteProvider(), LocalCacheProvider(), StaticProvider() ]) ``` **Environment-Specific Providers:** ```swift // Different providers for different environments let providers = [ EnvironmentProvider(environment: "production"), DefaultProvider() ] let multiProvider = MultiProvider(providers: providers) ``` ### InMemoryProvider `InMemoryProvider` resolves flags from a configuration you supply, with no network calls and no mocking. It is useful for demos, local development, and for testing hooks, providers, or application code that consumes flags. ```swift let provider = InMemoryProvider(flags: [ "boolean-flag": InMemoryFlag( variants: ["on": .boolean(true), "off": .boolean(false)], defaultVariant: "on"), "greeting": InMemoryFlag( variants: ["formal": .string("Good evening"), "casual": .string("hi")], defaultVariant: "formal", flagMetadata: ["version": .string("1.0.2")]), ]) await OpenFeatureAPI.shared.setProviderAndWait(provider: provider) OpenFeatureAPI.shared.getClient().getBooleanValue(key: "boolean-flag", defaultValue: false) // true ``` Targeting is expressed as a callback returning the key of the variant to resolve, or `nil` to fall back to the flag's `defaultVariant`: ```swift InMemoryFlag( variants: ["internal": .string("INTERNAL"), "external": .string("EXTERNAL")], defaultVariant: "external", contextEvaluator: { _, context in context?.getValue(key: "customer") == .boolean(false) ? "internal" : nil }) ``` A resolution reports `TARGETING_MATCH` when the callback selects a variant, `DEFAULT` when it returns `nil`, `STATIC` when the flag has no callback, and `DISABLED` for a flag constructed with `disabled: true`. An unknown flag key resolves as `FLAG_NOT_FOUND`, and a variant whose value is not of the requested type as `TYPE_MISMATCH`. The configuration can be changed after the provider is registered, which emits `PROVIDER_CONFIGURATION_CHANGED`: ```swift provider.updateFlag(key: "boolean-flag", flag: InMemoryFlag( variants: ["on": .boolean(true), "off": .boolean(false)], defaultVariant: "off")) provider.removeFlag(key: "greeting") // Replaces the whole configuration; the event reports the union of the old and new flag keys. provider.putConfiguration(["only-flag": InMemoryFlag(variants: ["on": .boolean(true)], defaultVariant: "on")]) ``` ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ```swift let cancellable = OpenFeatureAPI.shared.observe().sink { event in switch event { case ProviderEvent.ready: // ... default: // ... } } ``` ### Shutdown A shutdown function is not yet available in the iOS SDK. ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. You'll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. #### Status ownership Providers are fully responsible for managing their own status. The SDK reads `status` from the provider but never sets it. You must keep `status` consistent with the events you emit, and the property must be thread-safe (it can be read concurrently from flag evaluation paths). The easiest way to satisfy these requirements is to delegate to `ProviderStatusTracker`. #### Example implementation ```swift import Combine import OpenFeature final class CustomProvider: FeatureProvider { var hooks: [any Hook] = [] var metadata: ProviderMetadata = CustomMetadata() // ProviderStatusTracker keeps `status` in sync with emitted events, // handles thread safety, and replays the current status to new subscribers. private let statusTracker = ProviderStatusTracker() var status: ProviderStatus { statusTracker.status } func observe() -> AnyPublisher { statusTracker.observe() } func initialize(initialContext: EvaluationContext?) -> Future { Future { promise in // Perform context-aware initialisation, then emit any non-.notReady status. // .ready and .error are the most common outcomes. self.statusTracker.send(.ready(nil)) promise(.success(())) } } func onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext) -> Future { // Note: this may be called again before a previous lifecycle Future has // resolved. Cancel any in-flight async work when a new call arrives. Future { promise in self.statusTracker.send(.reconciling(nil)) // ... re-initialize with new context ... self.statusTracker.send(.contextChanged(nil)) // or .error(nil) on failure promise(.success(())) } } func getBooleanEvaluation( key: String, defaultValue: Bool, context: EvaluationContext? ) throws -> ProviderEvaluation { // resolve a boolean flag value } ... } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. Implement your own hook by conforming to the `Hook interface`. To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined. ```swift class BooleanHook: Hook { typealias HookValue = Bool func before(ctx: HookContext, hints: [String: Any]) { // do something } func after(ctx: HookContext, details: FlagEvaluationDetails, hints: [String: Any]) { // do something } func error(ctx: HookContext, error: Error, hints: [String: Any]) { // do something } func finally(ctx: HookContext, details: FlagEvaluationDetails, hints: [String: Any]) { // do something } } ``` #### Hook data Each hook gets its own `hookData` store on the `HookContext`. It is created fresh for every evaluation and shared across that hook's `before`, `after`, `error` and `finally` stages, but never with other hooks. Use it to carry per-evaluation state (a timer, a span, a correlation ID) from one stage to the next without touching the evaluation context. `EvaluationTimeHook` measures how long each evaluation takes by storing a timestamp in `before` and reading it back in `finally`: ```swift class EvaluationTimeHook: Hook { typealias HookValue = Bool func before(ctx: HookContext, hints: [String: Any]) { ctx.hookData["start"] = Date() } func finally(ctx: HookContext, details: FlagEvaluationDetails, hints: [String: Any]) { guard let start = ctx.hookData["start"] as? Date else { return } print("\(ctx.flagKey) evaluated in \(Date().timeIntervalSince(start))s") } } ``` `CorrelationIDHook` tags every log line of one evaluation with the same ID, whether it succeeds or fails. Because a correlation ID is useful for every flag, it overrides `supportsFlagValueType` to run for all value types rather than only `Bool`: ```swift class CorrelationIDHook: Hook { typealias HookValue = Bool func supportsFlagValueType(flagValueType: FlagValueType) -> Bool { true } func before(ctx: HookContext, hints: [String: Any]) { let correlationID = UUID().uuidString ctx.hookData["correlationID"] = correlationID print("[\(correlationID)] evaluating \(ctx.flagKey)") } func after(ctx: HookContext, details: FlagEvaluationDetails, hints: [String: Any]) { print("[\(correlationID(ctx))] \(ctx.flagKey) resolved to \(details.value)") } func error(ctx: HookContext, error: Error, hints: [String: Any]) { print("[\(correlationID(ctx))] \(ctx.flagKey) failed: \(error)") } private func correlationID(_ ctx: HookContext) -> String { ctx.hookData["correlationID"] as? String ?? "unknown" } } ``` Keys are strings and values can be of any type; hook data is never serialised or sent to the provider. #### Telemetry `Telemetry.createEvaluationEvent` turns a finished evaluation into an [OpenTelemetry-compatible event](https://openfeature.dev/specification/appendix-d.md): the name `feature_flag.evaluation` plus attributes such as `feature_flag.key`, `feature_flag.result.variant`, `feature_flag.result.value`, `feature_flag.result.reason`, `feature_flag.provider.name` and, when the evaluation failed, `error.type` and `error.message`. Call it from the `finally` stage, where the evaluation details are complete, and hand the result to your telemetry client as a span event or a log record. Attribute values are booleans, strings, integers or doubles; object and list flag values arrive as JSON strings, and attributes without a value are left out. ```swift class TelemetryHook: Hook { typealias HookValue = Bool /// Forwards an event to your telemetry client, e.g. `span.addEvent(name:attributes:)` or a log record. private let emit: (_ name: String, _ attributes: TelemetryAttributes) -> Void init(emit: @escaping (_ name: String, _ attributes: TelemetryAttributes) -> Void) { self.emit = emit } // Telemetry is useful for every flag, not only boolean ones. func supportsFlagValueType(flagValueType: FlagValueType) -> Bool { true } func finally(ctx: HookContext, details: FlagEvaluationDetails, hints: [String: Any]) { let event = Telemetry.createEvaluationEvent(hookContext: ctx, evaluationDetails: details) var attributes = event.attributes attributes[Telemetry.Attribute.value] = nil // drop the flag value when it may be sensitive or large emit(event.name, attributes) } } ``` Map each `TelemetryAttributeValue` to your client's attribute type with a `switch` over its four cases (`.boolean`, `.string`, `.integer`, `.double`). The SDK has no OpenTelemetry dependency and the utility takes no options: filtering, redacting or truncating attributes is the hook's responsibility, as the specification recommends. > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature Web SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v1.10.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/js-sdk/releases/tag/web-sdk-v1.10.0)
[![API Reference](https://img.shields.io/badge/reference-teal?logo=javascript\&logoColor=white)](https://open-feature.github.io/js-sdk/modules/_openfeature_web_sdk.html)[![NPM Download](https://img.shields.io/npm/dm/%40openfeature%2Fweb-sdk)](https://www.npmjs.com/package/@openfeature/web-sdk)[![codecov](https://codecov.io/gh/open-feature/js-sdk/branch/main/graph/badge.svg?token=3DC5XOEHMY)](https://codecov.io/gh/open-feature/js-sdk)[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6594/badge)](https://bestpractices.coreinfrastructure.org/projects/6594) ## Quick start ### Requirements * ES2015-compatible web browser (Chrome, Edge, Firefox, etc) ### Install #### npm ```sh npm install --save @openfeature/web-sdk ``` Tip This SDK is designed to run in the browser. If you're interested in server support, check out the [Node.js SDK](https://openfeature.dev/docs/reference/sdks/server/javascript.md). #### yarn ```sh # yarn requires manual installation of the @openfeature/core peer-dependency yarn add @openfeature/web-sdk @openfeature/core ``` Note `@openfeature/core` contains common components used by all OpenFeature JavaScript implementations. Every SDK version has a requirement on a single, specific version of this dependency. For more information, and similar implications on libraries developed with OpenFeature see [considerations when extending](#considerations). ### Usage ```ts import { OpenFeature } from '@openfeature/web-sdk'; // Register your feature flag provider try { await OpenFeature.setProviderAndWait(new YourProviderOfChoice()); } catch (error) { console.error('Failed to initialize provider:', error); } // create a new client const client = OpenFeature.getClient(); // Evaluate your feature flag const v2Enabled = client.getBooleanValue('v2_enabled', false); if (v2Enabled) { console.log('v2 is enabled'); } ``` ### API Reference See [here](https://open-feature.github.io/js-sdk/modules/_openfeature_web_sdk.html) for the complete API documentation. ## Features | Status | Features | Description | | ------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting-and-context) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations, particularly for A/B testing. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | | ✅ | [Multi-Provider](#multi-provider) | Combine multiple providers with configurable evaluation strategies. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Client-side\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=JavaScript) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: #### Awaitable To register a provider and ensure it is ready before further actions are taken, you can use the `setProviderAndWait` method as shown below: ```ts try { await OpenFeature.setProviderAndWait(new MyProvider()); } catch (error) { console.error('Failed to initialize provider:', error); } ``` #### Synchronous To register a provider in a synchronous manner, you can use the `setProvider` method as shown below: ```ts OpenFeature.setProvider(new MyProvider()); ``` Once the provider has been registered, the status can be tracked using [events](#eventing). In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. #### Multi-Provider The Multi-Provider allows you to use multiple underlying providers as sources of flag data for the OpenFeature web SDK. When a flag is being evaluated, the Multi-Provider will consult each underlying provider it is managing in order to determine the final result. Different evaluation strategies can be defined to control which providers get evaluated and which result is used. The Multi-Provider is a powerful tool for performing migrations between flag providers, or combining multiple providers into a single feature flagging interface. For example: * **Migration**: When migrating between two providers, you can run both in parallel under a unified flagging interface. As flags are added to the new provider, the Multi-Provider will automatically find and return them, falling back to the old provider if the new provider does not have the flag. * **Multiple Data Sources**: The Multi-Provider allows you to seamlessly combine many sources of flagging data, such as environment variables, local files, database values and SaaS hosted feature management systems. ```ts import { MultiProvider } from '@openfeature/web-sdk'; const multiProvider = new MultiProvider([{ provider: new ProviderA() }, { provider: new ProviderB() }]); await OpenFeature.setProviderAndWait(multiProvider); const client = OpenFeature.getClient(); console.log(client.getBooleanDetails('my-flag', false)); ``` By default, the Multi-Provider will evaluate all underlying providers in order and return the first successful result. If a provider indicates it does not have a flag (FLAG_NOT_FOUND error code), then it will be skipped and the next provider will be evaluated. ##### Evaluation Strategies The Multi-Provider comes with three strategies out of the box: * **FirstMatchStrategy** (default): Evaluates all providers in order and returns the first successful result. Providers that indicate FLAG_NOT_FOUND error will be skipped and the next provider will be evaluated. * **FirstSuccessfulStrategy**: Evaluates all providers in order and returns the first successful result. Any error will cause that provider to be skipped. * **ComparisonStrategy**: Evaluates all providers sequentially. If every provider returns a successful result with the same value, then that result is returned. Otherwise, the result returned by the configured "fallback provider" will be used. ```ts import { MultiProvider, FirstSuccessfulStrategy } from '@openfeature/web-sdk'; const multiProvider = new MultiProvider( [{ provider: new ProviderA() }, { provider: new ProviderB() }], new FirstSuccessfulStrategy(), ); ``` ##### Tracking Support The Multi-Provider supports tracking events across multiple providers, allowing you to send analytics events to all configured providers simultaneously: ```ts // Tracked events will be sent to all providers by default client.track('user-conversion', { value: 99.99, currency: 'USD', }); ``` ### Flag evaluation flow When a new provider is added to OpenFeature client the following process happens: ```mermaid sequenceDiagram autonumber Client-->+Feature Flag Provider: ResolveAll (context) Feature Flag Provider-->-Client: Flags values ``` In (1) the Client sends a request to the provider backend in order to get all values from all feature flags that it has. Once the provider backend replies (2) the client holds all flag values and therefore the flag evaluation process is synchronous. In order to prevent flag evaluations from defaulting while the provider is initializing, it is highly recommended to evaluate flags only after the provider is ready. This can be done using the `setProviderAndWait` method or using the `setProvider` method and listening for the `READY` [event](#eventing). ### Targeting and Context Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```ts // Sets global context during provider registration await OpenFeature.setProvider(new MyProvider(), { origin: document.location.host }); ``` Change context after the provider has been registered using `setContext`. ```ts // Set a value to the global context await OpenFeature.setContext({ targetingKey: localStorage.getItem('targetingKey') }); ``` Context is global and setting it is `async`. Providers may implement an `onContextChange` method that receives the old and newer contexts. Given a context change, providers can use this method internally to detect if the flag values cached on the client are still valid. If needed, a request will be made to the provider with the new context in order to get the correct flag values. ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Client-side\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=JavaScript) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```ts import { OpenFeature } from '@openfeature/web-sdk'; // add a hook globally, to run on all evaluations OpenFeature.addHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client const client = OpenFeature.getClient(); client.addHooks(new ExampleClientHook()); // add a hook for this evaluation only const boolValue = client.getBooleanValue('bool-flag', false, { hooks: [new ExampleHook()] }); ``` ### Logging The Web SDK will log warnings and errors to the console by default. This behavior can be overridden by passing a custom logger either globally or per client. A custom logger must implement the [Logger interface](https://github.com/open-feature/js-sdk/blob/main/packages/shared/src/logger/logger.ts). ```ts import type { Logger } from '@openfeature/web-sdk'; // The logger can be anything that conforms with the Logger interface const logger: Logger = console; // Sets a global logger OpenFeature.setLogger(logger); // Sets a client logger const client = OpenFeature.getClient(); client.setLogger(logger); ``` ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```ts import { OpenFeature, TypedInMemoryProvider } from '@openfeature/web-sdk'; const myFlags = { v2_enabled: { variants: { on: true, off: false, }, disabled: false, defaultVariant: 'on', }, } as const; // Registering the default provider OpenFeature.setProvider(new TypedInMemoryProvider(myFlags)); // Registering a provider to a domain OpenFeature.setProvider('my-domain', new TypedInMemoryProvider(someOtherFlags)); // A Client bound to the default provider const clientWithDefault = OpenFeature.getClient(); // A Client bound to the TypedInMemoryProvider provider const domainScopedClient = OpenFeature.getClient('my-domain'); ``` Domains can be defined on a provider during registration. For more details, please refer to the [providers](#providers) section. #### Manage evaluation context for domains By default, domain-scoped clients use the global context. This can be overridden by explicitly setting context when registering the provider or by referencing the domain when updating context: ```ts OpenFeature.setProvider('my-domain', new NewCachedProvider(), { targetingKey: localStorage.getItem('targetingKey') }); ``` To change context after the provider has been registered, use `setContext` with a domain: ```ts await OpenFeature.setContext('my-domain', { targetingKey: localStorage.getItem('targetingKey') }); ``` Once a domain's context has been defined, it will override the global context for all clients bound to the domain. Context can be cleared for a domain by calling `OpenFeature.clearContext("my-domain")` or `OpenFeature.clearContexts()` to reset all context. ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ```ts import { OpenFeature, ProviderEvents } from '@openfeature/web-sdk'; // OpenFeature API OpenFeature.addHandler(ProviderEvents.Ready, (eventDetails) => { console.log(`Ready event from: ${eventDetails?.providerName}:`, eventDetails); }); // Specific client const client = OpenFeature.getClient(); client.addHandler(ProviderEvents.Error, (eventDetails) => { console.log(`Error event from: ${eventDetails?.providerName}:`, eventDetails); }); ``` ### Tracking The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. ```ts // flag is evaluated client.getBooleanValue('new-feature', false); // new feature is used and track function is called recording the usage useNewFeature(); client.track('new-feature-used'); ``` ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```ts import { OpenFeature } from '@openfeature/web-sdk'; await OpenFeature.close(); ``` ### Type-Safe Flag Keys For enhanced type safety and autocompletion, you can override flag key types using TypeScript module augmentation. See the [`@openfeature/core` README](https://github.com/open-feature/js-sdk/blob/main/packages/shared/README.md#type-safe-flag-keys) for details. ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/js-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the [Provider interface](https://github.com/open-feature/js-sdk/blob/main/packages/web/src/provider/provider.ts) exported by the OpenFeature SDK. ```ts import { AnyProviderEvent, EvaluationContext, Hook, JsonValue, Logger, Provider, ProviderEventEmitter, ResolutionDetails, } from '@openfeature/web-sdk'; // implement the provider interface class MyProvider implements Provider { // Adds runtime validation that the provider is used with the expected SDK public readonly runsOn = 'client'; readonly metadata = { name: 'My Provider', } as const; // Optional provider managed hooks hooks?: Hook[]; resolveBooleanEvaluation( flagKey: string, defaultValue: boolean, context: EvaluationContext, logger: Logger, ): ResolutionDetails { // code to evaluate a boolean } resolveStringEvaluation( flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger, ): ResolutionDetails { // code to evaluate a string } resolveNumberEvaluation( flagKey: string, defaultValue: number, context: EvaluationContext, logger: Logger, ): ResolutionDetails { // code to evaluate a number } resolveObjectEvaluation( flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger, ): ResolutionDetails { // code to evaluate an object } onContextChange?(oldContext: EvaluationContext, newContext: EvaluationContext): Promise { // reconcile the provider's cached flags, if applicable } // implement with "new OpenFeatureEventEmitter()", and use "emit()" to emit events events?: ProviderEventEmitter | undefined; initialize?(context?: EvaluationContext | undefined): Promise { // code to initialize your provider } onClose?(): Promise { // code to shut down your provider } } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/js-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the [Hook interface](https://github.com/open-feature/js-sdk/blob/main/packages/shared/src/hooks/hook.ts). ```ts import type { Hook, HookContext, EvaluationDetails, FlagValue } from '@openfeature/web-sdk'; export class MyHook implements Hook { after(hookContext: HookContext, evaluationDetails: EvaluationDetails) { // code that runs after flag values are successfully resolved from the provider } } ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! ### Considerations When developing a library based on OpenFeature components, it's important to list the `@openfeature/web-sdk` as a `peerDependency` of your package. This is a general best-practice when developing JavaScript libraries that have dependencies in common with their consuming application. Failing to do this can result in multiple copies of the OpenFeature SDK in the consumer, which can lead to type errors, and broken singleton behavior. The `@openfeature/core` package itself follows this pattern: the `@openfeature/web-sdk` has a peer dependency on `@openfeature/core`, and uses whatever copy of that module the consumer has installed (note that NPM installs peers automatically unless `--legacy-peer-deps` is set, while yarn does not, and PNPM does so based on its configuration). When developing such libraries, it's NOT necessary to add a `peerDependency` on `@openfeature/core`, since the `@openfeature/web-sdk` establishes that dependency itself transitively. --- # OpenFeature Angular SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v1.3.2\&color=blue\&style=for-the-badge)](https://github.com/open-feature/js-sdk/releases/tag/angular-sdk-v1.3.2)
[![codecov](https://codecov.io/gh/open-feature/js-sdk/branch/main/graph/badge.svg?token=3DC5XOEHMY)](https://codecov.io/gh/open-feature/js-sdk)[![NPM Download](https://img.shields.io/npm/dm/%40openfeature%2Fangular-sdk)](https://www.npmjs.com/package/@openfeature/angular-sdk) ## Overview The OpenFeature Angular SDK adds Angular-specific functionality to the [OpenFeature Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md). In addition to the features provided by the [web sdk](https://openfeature.dev/docs/reference/sdks/client/web.md), capabilities include: * [Overview](#overview) * [Quick start](#quick-start) * [Requirements](#requirements) * [Install](#install) * [npm](#npm) * [yarn](#yarn) * [Required peer dependencies](#required-peer-dependencies) * [Usage](#usage) * [Standalone configuration (recommended)](#standalone-configuration-recommended) * [NgModule configuration (deprecated)](#ngmodule-configuration-deprecated) * [How to use](#how-to-use) * [Structural Directives](#structural-directives) * [Boolean Feature Flag](#boolean-feature-flag) * [Number Feature Flag](#number-feature-flag) * [String Feature Flag](#string-feature-flag) * [Object Feature Flag](#object-feature-flag) * [Opting-out of automatic re-rendering](#opting-out-of-automatic-re-rendering) * [Consuming the evaluation details](#consuming-the-evaluation-details) * [FeatureFlagService](#featureflagservice) * [Using with Observables](#using-with-observables) * [Using with Angular Signals](#using-with-angular-signals) * [Service Options](#service-options) * [Setting evaluation context](#setting-evaluation-context) * [Using a static object](#using-a-static-object) * [Using a factory function](#using-a-factory-function) * [Observability considerations](#observability-considerations) * [FAQ and troubleshooting](#faq-and-troubleshooting) * [Resources](#resources) ## Quick start ### Requirements * ES2015-compatible web browser (Chrome, Edge, Firefox, etc) * Angular version 16+ ### Install #### npm ```sh npm install --save @openfeature/angular-sdk ``` #### yarn ```sh # yarn requires manual installation of the peer dependencies (see below) yarn add @openfeature/angular-sdk @openfeature/web-sdk @openfeature/core ``` #### Required peer dependencies The following list contains the peer dependencies of `@openfeature/angular-sdk`. See the [package.json](https://github.com/open-feature/js-sdk/blob/main/packages/angular/projects/angular-sdk/package.json) for the required versions. * `@openfeature/web-sdk` * `@angular/common` * `@angular/core` ### Usage #### Standalone configuration (recommended) For standalone / `bootstrapApplication`-based apps, pass `provideOpenFeature()` in the `providers` array of your `ApplicationConfig`: ```typescript import { ApplicationConfig } from '@angular/core'; import { provideOpenFeature } from '@openfeature/angular-sdk'; export const appConfig: ApplicationConfig = { providers: [ provideOpenFeature({ provider: yourFeatureProvider, // domainBoundProviders are optional, mostly needed if more than one provider is used in the application. domainBoundProviders: { domain1: new YourOpenFeatureProvider(), domain2: new YourOtherOpenFeatureProvider(), }, }), ], }; ``` Then bootstrap your app with this config: ```typescript import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; import { appConfig } from './app/app.config'; bootstrapApplication(AppComponent, appConfig); ``` #### NgModule configuration (deprecated) Warning `OpenFeatureModule` and `forRoot()` are deprecated. Use [`provideOpenFeature()`](#standalone-configuration-recommended) instead. To configure OpenFeature for NgModule-based applications, import the `OpenFeatureModule` and call `forRoot` to register the provider(s) and optional evaluation context. ```typescript import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { OpenFeatureModule } from '@openfeature/angular-sdk'; @NgModule({ declarations: [ // Other components ], imports: [ CommonModule, OpenFeatureModule.forRoot({ provider: yourFeatureProvider, // domainBoundProviders are optional, mostly needed if more than one provider is used in the application. domainBoundProviders: { domain1: new YourOpenFeatureProvider(), domain2: new YourOtherOpenFeatureProvider(), }, }), ], }) export class AppModule {} ``` Note The feature flag directives (`BooleanFeatureFlagDirective`, `NumberFeatureFlagDirective`, `StringFeatureFlagDirective`, `ObjectFeatureFlagDirective`) and pipes are standalone and are not re-exported by `OpenFeatureModule`. In NgModule-based apps, you must also import the directives/pipes you use into the `imports` array of the component's NgModule (or of the component itself for standalone components). ##### Minimal Example You don't need to provide all the templates. Here's a minimal example using a boolean feature flag: If `initializing` and `reconciling` are not given, the feature flag value that is returned by the provider will determine what will be rendered. ```html
This is shown when the feature flag is enabled.
``` This example shows content when the feature flag `isFeatureEnabled` is true with a default value of true. No `else`, `initializing`, or `reconciling` templates are required in this case. #### How to use The library provides two main ways to work with feature flags: 1. **Structural Directives** - For template-based conditional rendering 2. **FeatureFlagService** - For programmatic access with Observables ##### Structural Directives The library provides four primary directives for feature flags, `booleanFeatureFlag`, `numberFeatureFlag`, `stringFeatureFlag` and `objectFeatureFlag`. The first value given to the directive is the flag key that should be evaluated. For all directives, the default value passed to OpenFeature has to be provided by the `default` parameter. For all non-boolean directives, the value to compare the evaluation result to can be provided by the `value` parameter. This parameter is optional, if omitted, the `thenTemplate` will always be rendered. The `domain` parameter is *optional* and will be used as domain when getting the OpenFeature provider. The `updateOnConfigurationChanged` and `updateOnContextChanged` parameter are *optional* and used to disable the automatic re-rendering on flag value or context change. They are set to `true` by default. The template referenced in `else` will be rendered if the evaluated feature flag is `false` for the `booleanFeatureFlag` directive and if the `value` does not match evaluated flag value for all other directives. This parameter is *optional*. The template referenced in `initializing` and `reconciling` will be rendered if OpenFeature provider is in the corresponding states. This parameter is *optional*, if omitted, the `then` and `else` templates will be rendered according to the flag value. ###### Boolean Feature Flag ```html
This is shown when the feature flag is enabled.
This is shown when the feature flag is disabled. This is shown when the feature flag is initializing. This is shown when the feature flag is reconciling. ``` ###### Number Feature Flag ```html
This is shown when the feature flag matches the specified discount rate.
This is shown when the feature flag does not match the specified discount rate. This is shown when the feature flag is initializing. This is shown when the feature flag is reconciling. ``` ###### String Feature Flag ```html
This is shown when the feature flag matches the specified theme color.
This is shown when the feature flag does not match the specified theme color. This is shown when the feature flag is initializing. This is shown when the feature flag is reconciling. ``` ###### Object Feature Flag ```html
This is shown when the feature flag matches the specified user configuration.
This is shown when the feature flag does not match the specified user configuration. This is shown when the feature flag is initializing. This is shown when the feature flag is reconciling. ``` ###### Opting-out of automatic re-rendering By default, the directive re-renders when the flag value changes or the context changes. In cases, this is not desired, re-rendering can be disabled for both events: ```html
This is shown when the feature flag is enabled.
``` ###### Consuming the evaluation details The `evaluation details` can be used when rendering the templates. The directives [`$implicit`](https://angular.dev/guide/directives/structural-directives#structural-directive-shorthand) value will be bound to the flag value and additionally the value `evaluationDetails` will be bound to the whole evaluation details. They can be referenced in all templates. The following example shows `value` being implicitly bound and `details` being bound to the evaluation details. ```html
It was a match! The theme color is {{ value }} because of {{ details.reason }}
It was no match! The theme color is {{ value }} because of {{ details.reason }} ``` When the expected flag value is omitted, the template will always be rendered. This can be used to just render the flag value or details without conditional rendering. ```html
The theme color is {{ value }}.
``` ##### FeatureFlagService The `FeatureFlagService` provides programmatic access to feature flags through reactive patterns. All methods return Observables that automatically emit new values when flag configurations or evaluation context changes. ###### Using with Observables ```typescript import { Component, inject } from '@angular/core'; import { AsyncPipe } from '@angular/common'; import { FeatureFlagService } from '@openfeature/angular-sdk'; @Component({ selector: 'my-component', standalone: true, imports: [AsyncPipe], template: `
Feature is enabled! Reason: {{ (isFeatureEnabled$ | async)?.reason }}
Theme: {{ (currentTheme$ | async)?.value }}
Max items: {{ (maxItems$ | async)?.value }}
`, }) export class MyComponent { private flagService = inject(FeatureFlagService); // Boolean flag isFeatureEnabled$ = this.flagService.getBooleanDetails('my-feature', false); // String flag currentTheme$ = this.flagService.getStringDetails('theme', 'light'); // Number flag maxItems$ = this.flagService.getNumberDetails('max-items', 10); // Object flag with type safety config$ = this.flagService.getObjectDetails<{ timeout: number }>('api-config', { timeout: 5000 }); } ``` ###### Using with Angular Signals You can convert any Observable from the service to an Angular Signal using `toSignal()`: ```typescript import { Component, inject } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FeatureFlagService } from '@openfeature/angular-sdk'; @Component({ selector: 'my-component', standalone: true, template: `
Feature is enabled! Reason: {{ isFeatureEnabled()?.reason }}
Theme: {{ currentTheme()?.value }}
`, }) export class MyComponent { private flagService = inject(FeatureFlagService); // Convert Observables to Signals isFeatureEnabled = toSignal(this.flagService.getBooleanDetails('my-feature', false)); currentTheme = toSignal(this.flagService.getStringDetails('theme', 'light')); } ``` ###### Service Options The service methods accept the [same options as the directives](#opting-out-of-automatic-re-rendering): ```typescript const flag$ = this.flagService.getBooleanDetails('my-flag', false, 'my-domain', { updateOnConfigurationChanged: false, // default: true updateOnContextChanged: false, // default: true }); ``` ##### Setting evaluation context To set the initial evaluation context, you can add the `context` parameter to the `provideOpenFeature()` configuration. This context can be either an object or a factory function that returns an `EvaluationContext`. Tip Updating the context can be done directly via the global OpenFeature API using `OpenFeature.setContext()` Here's how you can define and use the initial client evaluation context: ###### Using a static object ```typescript import { ApplicationConfig } from '@angular/core'; import { provideOpenFeature } from '@openfeature/angular-sdk'; const initialContext = { user: { id: 'user123', role: 'admin', }, }; export const appConfig: ApplicationConfig = { providers: [ provideOpenFeature({ provider: yourFeatureProvider, context: initialContext, }), ], }; ``` ###### Using a factory function ```typescript import { ApplicationConfig } from '@angular/core'; import { provideOpenFeature, EvaluationContext } from '@openfeature/angular-sdk'; const contextFactory = (): EvaluationContext => loadContextFromLocalStorage(); export const appConfig: ApplicationConfig = { providers: [ provideOpenFeature({ provider: yourFeatureProvider, context: contextFactory, }), ], }; ``` ##### Observability considerations Angular's lifecycle can result in flags being evaluated multiple times as a user interacts with a page. If you are using an OpenFeature hook for telemetry, this can result in inflated evaluation metrics. The [OpenFeature debounce hook](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/hooks/debounce) can help to reduce the amount of redundant evaluations reported to your observability platform by limiting the frequency at which evaluation metrics are reported. #### Type-Safe Flag Keys For enhanced type safety and autocompletion, you can override flag key types using TypeScript module augmentation. See the [`@openfeature/core` README](https://github.com/open-feature/js-sdk/blob/main/packages/shared/README.md#type-safe-flag-keys) for details. ## FAQ and troubleshooting > I can import things form the `@openfeature/angular-sdk`, `@openfeature/web-sdk`, and `@openfeature/core`; which should I use? The `@openfeature/angular-sdk` re-exports everything from its peers (`@openfeature/web-sdk` and `@openfeature/core`), and adds the Angular-specific features. You can import everything from the `@openfeature/angular-sdk` directly. Avoid importing anything from `@openfeature/web-sdk` or `@openfeature/core`. ## Resources * [Example repo](https://github.com/open-feature/angular-test-app) --- # OpenFeature React SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v1.4.1\&color=blue\&style=for-the-badge)](https://github.com/open-feature/js-sdk/releases/tag/react-sdk-v1.4.1)
[![codecov](https://codecov.io/gh/open-feature/js-sdk/branch/main/graph/badge.svg?token=3DC5XOEHMY)](https://codecov.io/gh/open-feature/js-sdk)[![NPM Download](https://img.shields.io/npm/dm/%40openfeature%2Freact-sdk)](https://www.npmjs.com/package/@openfeature/react-sdk) ## Overview The OpenFeature React SDK adds React-specific functionality to the [OpenFeature Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md). In addition to the feature provided by the [web sdk](https://openfeature.dev/docs/reference/sdks/client/web.md), capabilities include: * [Overview](#overview) * [Quick start](#quick-start) * [Requirements](#requirements) * [Install](#install) * [npm](#npm) * [yarn](#yarn) * [Required peer dependencies](#required-peer-dependencies) * [Usage](#usage) * [OpenFeatureProvider context provider](#openfeatureprovider-context-provider) * [Evaluation hooks](#evaluation-hooks) * [Declarative components](#declarative-components) * [FeatureFlag Component](#featureflag-component) * [Multiple Providers and Domains](#multiple-providers-and-domains) * [Re-rendering with Context Changes](#re-rendering-with-context-changes) * [Re-rendering with Flag Configuration Changes](#re-rendering-with-flag-configuration-changes) * [Suspense Support](#suspense-support) * [Tracking](#tracking) * [Observability Considerations](#observability-considerations) * [Testing](#testing) * [FAQ and troubleshooting](#faq-and-troubleshooting) * [Resources](#resources) ## Quick start ### Requirements * ES2015-compatible web browser (Chrome, Edge, Firefox, etc) * React version 16.8+ ### Install #### npm ```sh npm install --save @openfeature/react-sdk ``` #### yarn ```sh # yarn requires manual installation of the peer dependencies (see below) yarn add @openfeature/react-sdk @openfeature/web-sdk @openfeature/core ``` #### Required peer dependencies The following list contains the peer dependencies of `@openfeature/react-sdk`. See the [package.json](https://github.com/open-feature/js-sdk/blob/main/packages/react/package.json) for the required versions. * `@openfeature/web-sdk` * `react` ### Usage #### OpenFeatureProvider context provider The `OpenFeatureProvider` is a [React context provider](https://react.dev/reference/react/createContext#provider) which represents a scope for feature flag evaluations within a React application. It binds an OpenFeature client to all evaluations within child components, and allows the use of evaluation hooks. The example below shows how to use the `OpenFeatureProvider` with OpenFeature's `TypedInMemoryProvider`. ```tsx import { EvaluationContext, OpenFeatureProvider, useFlag, OpenFeature, TypedInMemoryProvider, } from '@openfeature/react-sdk'; const flagConfig = { 'new-message': { disabled: false, variants: { on: true, off: false, }, defaultVariant: 'on', contextEvaluator: (context: EvaluationContext) => { if (context.silly) { return 'on'; } return 'off'; }, }, } as const; // Instantiate and set our provider (be sure this only happens once)! // Note: there's no need to await its initialization, the React SDK handles re-rendering and suspense for you! OpenFeature.setProvider(new TypedInMemoryProvider(flagConfig)); // Enclose your content in the configured provider function App() { return ( ); } ``` #### Evaluation hooks Within the provider, you can use the various evaluation hooks to evaluate flags. ```tsx function Page() { // Use the "query-style" flag evaluation hook, specifying a flag-key and a default value. const { value: showNewMessage } = useFlag('new-message', true); return (
{showNewMessage ?

Welcome to this OpenFeature-enabled React app!

:

Welcome to this React app.

}
); } ``` You can use the strongly typed flag value and flag evaluation detail hooks as well if you prefer. ```tsx import { useBooleanFlagValue } from '@openfeature/react-sdk'; // boolean flag evaluation const value = useBooleanFlagValue('new-message', false); ``` ```tsx import { useBooleanFlagDetails } from '@openfeature/react-sdk'; // "detailed" boolean flag evaluation const { value, variant, reason, flagMetadata } = useBooleanFlagDetails('new-message', false); ``` #### Declarative components The React SDK includes declarative components for feature flagging that provide a more JSX-native approach to conditional rendering. ##### FeatureFlag Component The `FeatureFlag` component conditionally renders its children based on feature flag evaluation: ```tsx import { FeatureFlag } from '@openfeature/react-sdk'; function App() { return ( {/* Basic usage - renders children when flag is truthy */} {/* Match specific values */} {/* Boolean flag with fallback */} }> {/* Custom predicate function for complex matching */} !!expected && actual.value.includes(expected)} > {/* Function as children for accessing flag details */} {({ value, reason }) => ( value is {value}, reason is {reason?.toString()} )} ); } ``` The `FeatureFlag` component supports the following props: * **`flagKey`** (required): The feature flag key to evaluate * **`defaultValue`** (required): Default value when the flag is not available * **`matchValue`** (required, except for boolean flags): Value to match against the flag value. By default, an optimized deep-comparison function is used. * **`predicate`** (optional): Custom function for matching logic that receives the expected value and evaluation details * **`children`**: Content to render when condition is met (can be JSX or a function receiving flag details) * **`fallback`** (optional): Content to render when condition is not met #### Multiple Providers and Domains Multiple providers can be used by passing a `domain` to the `OpenFeatureProvider`: ```tsx // Flags within this domain will use the client/provider associated with `my-domain`, function App() { return ( ); } ``` This is analogous to: ```ts OpenFeature.getClient('my-domain'); ``` Alternatively, a pre-configured `Client` instance can be passed directly via the `client` prop: ```tsx const client = OpenFeature.getClient('my-domain'); function App() { return ( ); } ``` The `domain` and `client` props are mutually exclusive. For more information about `domains`, refer to the [web SDK](https://github.com/open-feature/js-sdk/blob/main/packages/web/README.md). #### Re-rendering with Context Changes By default, if the OpenFeature [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) is modified, components will be re-rendered. This is useful in cases where flag values are dependant on user-attributes or other application state (user logged in, items in card, etc). You can disable this feature in the hook options (or in the [OpenFeatureProvider](#openfeatureprovider-context-provider)): ```tsx function Page() { const { value: showNewMessage } = useFlag('new-message', false, { updateOnContextChanged: false }); return (
{showNewMessage ?

Welcome to this OpenFeature-enabled React app!

:

Welcome to this React app.

}
); } ``` For more information about how evaluation context works in the React SDK, see the documentation on OpenFeature's [static context SDK paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm). #### Re-rendering with Flag Configuration Changes By default, if the underlying provider emits a `ConfigurationChanged` event, components will be re-rendered. This is useful if you want your UI to immediately reflect changes in the backend flag configuration. You can disable this feature in the hook options (or in the [OpenFeatureProvider](#openfeatureprovider-context-provider)): ```tsx function Page() { const { value: showNewMessage } = useFlag('new-message', false, { updateOnConfigurationChanged: false }); return (
{showNewMessage ?

Welcome to this OpenFeature-enabled React app!

:

Welcome to this React app.

}
); } ``` If your provider doesn't support updates, this configuration has no impact. Note If your provider includes a list of [flags changed](https://open-feature.github.io/js-sdk/types/_openfeature_server_sdk.ConfigChangeEvent.html) in its `PROVIDER_CONFIGURATION_CHANGED` event, that list of flags is used to decide which flag evaluation hooks should re-run by diffing the latest value of these flags with the previous render. If your provider event does not the include the `flags changed` list, then the SDK diffs all flags with the previous render to determine which hooks should re-run. #### Suspense Support Note React suspense is an experimental feature and is subject to change in future versions. Frequently, providers need to perform some initial startup tasks. It may be desirable not to display components with feature flags until this is complete or when the context changes. Built-in [suspense](https://react.dev/reference/react/Suspense) support makes this easy. Use `useSuspenseFlag` or pass `{ suspend: true }` in the hook options to leverage this functionality. ```tsx function Content() { // cause the "fallback" to be displayed if the component uses feature flags and the provider is not ready return ( }> ); } function Message() { // component to render after READY, equivalent to useFlag('new-message', false, { suspend: true }); const { value: showNewMessage } = useSuspenseFlag('new-message', false); return ( <> {showNewMessage ? (

Welcome to this OpenFeature-enabled React app!

) : (

Welcome to this plain old React app!

)} ); } function Fallback() { // component to render before READY. return

Waiting for provider to be ready...

; } ``` This can be disabled in the hook options (or in the [OpenFeatureProvider](#openfeatureprovider-context-provider)). #### Tracking The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](https://openfeature.dev/docs/reference/sdks/client/web.md#hooks) or [provider](https://openfeature.dev/docs/reference/sdks/client/web.md#providers) can be associated with telemetry reported in the client's `track` function. The React SDK includes a hook for firing tracking events in the `` context in use: ```tsx function MyComponent() { // get a tracking function for this . const { track } = useTrack(); // call the tracking event // can be done in render, useEffect, or in handlers, depending on your use case track(eventName, trackingDetails); return <>...; } ``` #### Observability Considerations React's lifecycle can result in flags being evaluated multiple times as a user interacts with a page. If you are using an OpenFeature hook for telemetry, this can result in inflated evaluation metrics. The [OpenFeature debounce hook](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/hooks/debounce) can help to reduce the amount of redundant evaluations reported to your observability platform by limiting the frequency at which evaluation metrics are reported. #### Type-Safe Flag Keys For enhanced type safety and autocompletion, you can override flag key types using TypeScript module augmentation. See the [`@openfeature/core` README](https://github.com/open-feature/js-sdk/blob/main/packages/shared/README.md#type-safe-flag-keys) for details. ### Testing The React SDK includes a built-in context provider for testing. This allows you to easily test components that use evaluation hooks (such as `useFlag`) or declarative components (such as `FeatureFlag`). If you try to test a component (in this case, `MyComponent`) which uses feature flags, you might see an error message like: > No OpenFeature client available - components using OpenFeature must be wrapped with an ``. You can resolve this by simply wrapping your component under test in the OpenFeatureTestProvider: ```tsx // use default values for all evaluations ``` The basic configuration above will simply use the default value provided in code. If you'd like to control the values returned by the evaluation hooks, you can pass a map of flag keys and values: ```tsx // return `true` for all evaluations of `'my-boolean-flag'` // testing declarative FeatureFlag components ``` Additionally, you can pass an artificial delay for the provider startup to test your suspense boundaries or loaders/spinners impacted by feature flags: ```tsx // delay the provider start by 1000ms and then return `true` for all evaluations of `'my-boolean-flag'` ``` For maximum control, you can also pass your own mock provider implementation. The type of this option is `Partial`, so you can pass an incomplete implementation: ```tsx class MyTestProvider implements Partial { // implement the relevant resolver resolveBooleanEvaluation(): ResolutionDetails { return { value: true, variant: 'my-variant', reason: 'MY_REASON', }; } } ``` ```tsx // use your custom testing provider , ``` ## FAQ and troubleshooting > I get an error that says something like: `A React component suspended while rendering, but no fallback UI was specified.` The OpenFeature React SDK features built-in [suspense support](#suspense-support). This means that it will render your loading fallback automatically while your provider starts up and during context reconciliation for any of your components using feature flags! If you use suspense and neglect to create a suspense boundary around any components using feature flags, you will see this error. Add a suspense boundary to resolve this issue. Alternatively, you can disable this suspense (the default) by removing `suspendWhileReconciling=true`, `suspendUntilReady=true` or `suspend=true` in the [evaluation hooks](#evaluation-hooks) or the [OpenFeatureProvider](#openfeatureprovider-context-provider) (which applies to all evaluation hooks in child components). > I get odd rendering issues or errors when components mount if I use the suspense features. In React 16/17's "Legacy Suspense", when a component suspends, its sibling components initially mount and then are hidden. This can cause surprising effects and inconsistencies if sibling components are rendered while the provider is still getting ready. To fix this, you can upgrade to React 18, which uses "Concurrent Suspense", in which siblings are not mounted until their suspended sibling resolves. Alternatively, if you cannot upgrade to React 18, you can use the `useWhenProviderReady` utility hook in any sibling components to prevent them from mounting until the provider is ready. > I need to distinguish a provider that is still starting from one that failed to start. `useWhenProviderReady` is a boolean gate: it returns `false` both while the provider is `NOT_READY` and after a terminal `ERROR` or `FATAL` state. When a component needs to show a loader only while the provider is starting, use `useOpenFeatureClientStatus` and handle terminal states explicitly: ```tsx import { ProviderStatus, useOpenFeatureClientStatus } from '@openfeature/react-sdk'; function ProviderAwareSibling() { const status = useOpenFeatureClientStatus(); if (status === ProviderStatus.NOT_READY) { return ; } if (status === ProviderStatus.ERROR || status === ProviderStatus.FATAL) { // Evaluation hooks continue with their code defaults. return ; } return ; } ``` > I am using multiple `OpenFeatureProvider` contexts, but they share the same provider or evaluation context. Why? The `OpenFeatureProvider` binds a `client` to all child components, but the provider and context associated with that client is controlled by the `domain` parameter. This is consistent with all OpenFeature SDKs. To scope an OpenFeatureProvider to a particular provider/context, set the `domain` parameter on your `OpenFeatureProvider`: ```tsx ``` > I can import things form the `@openfeature/react-sdk`, `@openfeature/web-sdk`, and `@openfeature/core`; which should I use? The `@openfeature/react-sdk` re-exports everything from its peers (`@openfeature/web-sdk` and `@openfeature/core`) and adds the React-specific features. You can import everything from the `@openfeature/react-sdk` directly. Avoid importing anything from `@openfeature/web-sdk` or `@openfeature/core`. ## Resources * [Example repo](https://github.com/open-feature/react-test-app) --- # SDK Compatibility Overview This page provides an overview of the status and supported features of the OpenFeature SDKs. ### Server-side SDKs Server-side SDKs are intended for multi-user applications (e.g. web server applications) and conform to the [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm). | | [Java](https://openfeature.dev/docs/reference/sdks/server/java.md) | [Node.js](https://openfeature.dev/docs/reference/sdks/server/javascript.md) | [.NET](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) | [Go](https://openfeature.dev/docs/reference/sdks/server/go.md) | [Python](https://openfeature.dev/docs/reference/sdks/server/python.md) | [PHP](https://openfeature.dev/docs/reference/sdks/server/php.md) | [Ruby](https://openfeature.dev/docs/reference/sdks/server/ruby.md) | [Dart](https://openfeature.dev/docs/reference/sdks/server/dart.md) | [Rust](https://openfeature.dev/docs/reference/sdks/server/rust.md) | [C++](https://openfeature.dev/docs/reference/sdks/server/cpp.md) | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Status | | | | | | | | | | | | Spec versionThe version of the specification that the SDK is fully compliant with. | [0.7.0](https://github.com/open-feature/spec/releases/tag/v0.7.0) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.7.0](https://github.com/open-feature/spec/releases/tag/v0.7.0) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.5.1](https://github.com/open-feature/spec/releases/tag/v0.5.1) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.5.2](https://github.com/open-feature/spec/releases/tag/v0.5.2) | [0.2.0](https://github.com/open-feature/spec/releases/tag/v0.2.0) | | Release versionThe latest published release version. | [1.23.0](https://github.com/open-feature/java-sdk/releases/tag/v1.23.0) | [1.23.0](https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.23.0) | [2.14.1](https://github.com/open-feature/dotnet-sdk/releases/tag/v2.14.1) | [1.19.0](https://github.com/open-feature/go-sdk/releases/tag/v1.19.0) | [0.10.0](https://github.com/open-feature/python-sdk/releases/tag/v0.10.0) | [2.3.0](https://github.com/open-feature/php-sdk/releases/tag/2.3.0) | [0.6.5](https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.5) | [0.1.0](https://github.com/open-feature/dart-sdk/releases/tag/v0.1.0) | [0.3.0](https://github.com/open-feature/rust-sdk/releases/tag/open-feature-v0.3.0) | [0.0.1](https://github.com/open-feature/cpp-sdk/releases/tag/v0.0.1) | | Stable releaseOpenFeature employs semantic versioning for release versions. SDKs below 1.0 aim to minimize breaking changes but are allowed in order to avoid long-term technical debt.- ✅: A major version 1 or greater is available
- ⚠️: A major version hasn't been released | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | | Features | | | | | | | | | | | | Providers | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/php.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#providers) | [⚠️](https://openfeature.dev/docs/reference/sdks/server/cpp.md#providers) | | Targeting | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/php.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/server/cpp.md#targeting) | | Hooks | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/php.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#hooks) | [❌](https://openfeature.dev/docs/reference/sdks/server/cpp.md#hooks) | | Logging | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/php.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#logging) | [❌](https://openfeature.dev/docs/reference/sdks/server/cpp.md#logging) | | Domains / Named clients | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#domains) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#domains) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#domains) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#domains) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#domains) | [❌](https://openfeature.dev/docs/reference/sdks/server/php.md#named-clients) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#domains) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#domains) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#named-clients) | [✅](https://openfeature.dev/docs/reference/sdks/server/cpp.md#domains) | | Eventing | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#eventing) | [⚠️](https://openfeature.dev/docs/reference/sdks/server/php.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#eventing) | [❌](https://openfeature.dev/docs/reference/sdks/server/rust.md#eventing) | [❌](https://openfeature.dev/docs/reference/sdks/server/cpp.md#eventing) | | Tracking | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#tracking) | [❓](https://openfeature.dev/docs/reference/sdks/server/php.md) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#tracking) | [❓](https://openfeature.dev/docs/reference/sdks/server/rust.md) | [❌](https://openfeature.dev/docs/reference/sdks/server/cpp.md#tracking) | | Transaction Context Propagation | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#transaction-context-propagation) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#transaction-context-propagation) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#transaction-context-propagation) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#transaction-context-propagation) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#transaction-context-propagation) | [❓](https://openfeature.dev/docs/reference/sdks/server/php.md) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#transaction-context-propagation) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#transaction-context-propagation) | [❓](https://openfeature.dev/docs/reference/sdks/server/rust.md) | [❌](https://openfeature.dev/docs/reference/sdks/server/cpp.md#transaction-context-propagation) | | Shutdown | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#shutdown) | [❌](https://openfeature.dev/docs/reference/sdks/server/php.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/server/cpp.md#shutdown) | | Extending | [✅](https://openfeature.dev/docs/reference/sdks/server/java.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/dotnet.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/go.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/python.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/php.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/ruby.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/dart.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/rust.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/server/cpp.md#extending) | | Multi-Provider | [❓](https://openfeature.dev/docs/reference/sdks/server/java.md) | [✅](https://openfeature.dev/docs/reference/sdks/server/javascript.md#multi-provider) | [❓](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) | [❓](https://openfeature.dev/docs/reference/sdks/server/go.md) | [❓](https://openfeature.dev/docs/reference/sdks/server/python.md) | [❓](https://openfeature.dev/docs/reference/sdks/server/php.md) | [❓](https://openfeature.dev/docs/reference/sdks/server/ruby.md) | [⚠️](https://openfeature.dev/docs/reference/sdks/server/dart.md#multi-provider-experimental) | [❓](https://openfeature.dev/docs/reference/sdks/server/rust.md) | [❓](https://openfeature.dev/docs/reference/sdks/server/cpp.md) | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ | Unknown status: ❓ ### Client-side SDKs Client-side SDKs are intended for single user client applications (e.g. mobile phones, single-page web apps) and conform to the [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm). | | [Web](https://openfeature.dev/docs/reference/sdks/client/web.md) | [Kotlin](https://openfeature.dev/docs/reference/sdks/client/kotlin.md) | [iOS](https://openfeature.dev/docs/reference/sdks/client/swift.md) | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Status | | | | | Spec versionThe version of the specification that the SDK is fully compliant with. | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | [0.8.0](https://github.com/open-feature/spec/releases/tag/v0.8.0) | | Release versionThe latest published release version. | [1.10.0](https://github.com/open-feature/js-sdk/releases/tag/web-sdk-v1.10.0) | [0.9.0](https://github.com/open-feature/kotlin-sdk/releases/tag/v0.9.0) | [0.7.0](https://github.com/open-feature/swift-sdk/releases/tag/0.7.0) | | Stable releaseOpenFeature employs semantic versioning for release versions. SDKs below 1.0 aim to minimize breaking changes but are allowed in order to avoid long-term technical debt.- ✅: A major version 1 or greater is available
- ⚠️: A major version hasn't been released | ✅ | ⚠️ | ⚠️ | | Features | | | | | Providers | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#providers) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#providers) | | Targeting | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#targeting-and-context) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#targeting) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#targeting) | | Hooks | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#hooks) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#hooks) | | Logging | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#logging) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#logging) | | Domains / Named clients | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#domains) | [⚠️](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#domains) | [❌](https://openfeature.dev/docs/reference/sdks/client/swift.md#domains) | | Eventing | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#eventing) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#eventing) | | Tracking | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#tracking) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#tracking) | | Shutdown | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#shutdown) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#shutdown) | [❌](https://openfeature.dev/docs/reference/sdks/client/swift.md#shutdown) | | Extending | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#extending) | [✅](https://openfeature.dev/docs/reference/sdks/client/swift.md#extending) | | Multi-Provider | [✅](https://openfeature.dev/docs/reference/sdks/client/web.md#multi-provider) | [✅](https://openfeature.dev/docs/reference/sdks/client/kotlin.md#multi-provider) | [❓](https://openfeature.dev/docs/reference/sdks/client/swift.md) | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ | Unknown status: ❓ --- # Server OpenFeature SDKs and components for server-side usage. ## Languages, frameworks, and runtimes # [.NET SDK](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) ## [The official OpenFeature SDK for .NET](https://openfeature.dev/docs/reference/sdks/server/dotnet.md) # [C++ SDK](https://openfeature.dev/docs/reference/sdks/server/cpp.md) ## [The official OpenFeature SDK for C++](https://openfeature.dev/docs/reference/sdks/server/cpp.md) # [Dart SDK](https://openfeature.dev/docs/reference/sdks/server/dart.md) ## [The official OpenFeature SDK for Dart](https://openfeature.dev/docs/reference/sdks/server/dart.md) # [Go SDK](https://openfeature.dev/docs/reference/sdks/server/go.md) ## [The official OpenFeature SDK for Go](https://openfeature.dev/docs/reference/sdks/server/go.md) # [Java SDK](https://openfeature.dev/docs/reference/sdks/server/java.md) ## [The official OpenFeature SDK for Java](https://openfeature.dev/docs/reference/sdks/server/java.md) # [NestJS SDK](https://openfeature.dev/docs/reference/sdks/server/javascript/nestjs.md) ## [The official OpenFeature SDK for NestJS](https://openfeature.dev/docs/reference/sdks/server/javascript/nestjs.md) # [Node.js SDK](https://openfeature.dev/docs/reference/sdks/server/javascript.md) ## [The official OpenFeature SDK for Node.js](https://openfeature.dev/docs/reference/sdks/server/javascript.md) # [PHP SDK](https://openfeature.dev/docs/reference/sdks/server/php.md) ## [The official OpenFeature SDK for PHP](https://openfeature.dev/docs/reference/sdks/server/php.md) # [Python SDK](https://openfeature.dev/docs/reference/sdks/server/python.md) ## [The official OpenFeature SDK for Python](https://openfeature.dev/docs/reference/sdks/server/python.md) # [Ruby SDK](https://openfeature.dev/docs/reference/sdks/server/ruby.md) ## [The official OpenFeature SDK for Ruby](https://openfeature.dev/docs/reference/sdks/server/ruby.md) # [Rust SDK](https://openfeature.dev/docs/reference/sdks/server/rust.md) ## [The official OpenFeature SDK for Rust](https://openfeature.dev/docs/reference/sdks/server/rust.md) --- # OpenFeature C++ SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.2.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.2.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.0.1\&color=blue\&style=for-the-badge)](https://github.com/open-feature/cpp-sdk/releases/tag/v0.0.1)
[![CII Best Practices](https://www.bestpractices.dev/projects/11271/badge)](https://www.bestpractices.dev/en/projects/11271) ## Quick start ### Requirements * C++17 or newer (due to the usage of `std::optional`, `std::any`, `std::variant`, and `std::string_view`) * [Bazel](https://bazel.build/) build system ### Install To use the OpenFeature C++ SDK in your Bazel project, add the dependency to your `MODULE.bazel` file or your `WORKSPACE` (depending on your Bazel configuration). ### Usage ```cpp void example() { // flags defined in memory std::unordered_map my_flags = { {"v2_enabled", true} }; // configure a provider openfeature::OpenFeatureAPI& api = openfeature::OpenFeatureAPI::GetInstance(); api.SetProviderAndWait(std::make_shared(my_flags)); // create a client std::shared_ptr client = api.GetClient(); // get a bool flag value bool flag_value = client->GetBooleanValue("v2_enabled", false); } ``` ### API Reference ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ⚠️ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ❌ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ❌ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ❌ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ❌ | [Tracking](#tracking) | Associate user-actions with flag evaluations for the purposes of experimentation. | | ❌ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread) | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=cpp) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: #### Synchronous To register a provider in a blocking manner to ensure it is ready before further actions are taken, use the `SetProviderAndWait` method: ```cpp openfeature::OpenFeatureAPI& api = openfeature::OpenFeatureAPI::GetInstance(); api.SetProviderAndWait(std::make_shared()); ``` #### Asynchronous To register a provider in a non-blocking manner, use the `SetProvider` method: ```cpp openfeature::OpenFeatureAPI::GetInstance().SetProvider(std::make_shared()); ``` In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```cpp openfeature::OpenFeatureAPI& api = openfeature::OpenFeatureAPI::GetInstance(); // Set a global evaluation context openfeature::EvaluationContext global_ctx = openfeature::EvaluationContext::Builder() .WithAttribute("region", "us-east-1") .Build(); api.SetEvaluationContext(global_ctx); // Set a client-level evaluation context std::shared_ptr client = api.GetClient(); openfeature::EvaluationContext client_ctx = openfeature::EvaluationContext::Builder() .WithAttribute("app_version", "1.0.5") .Build(); client->SetEvaluationContext(client_ctx); // Provide an invocation-level evaluation context openfeature::EvaluationContext req_ctx = openfeature::EvaluationContext::Builder() .WithTargetingKey("session-id-12345") .WithAttribute("email", "user@example.com") .Build(); bool flag_value = client->GetBooleanValue("some-flag", false, req_ctx); ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=cpp) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ### Logging ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```cpp openfeature::OpenFeatureAPI& api = openfeature::OpenFeatureAPI::GetInstance(); // registering the default provider api.SetProvider(std::make_shared()); // registering a provider to a domain api.SetProvider("my-domain", std::make_shared()); // A client bound to the default provider std::shared_ptr client_default = api.GetClient(); // A client bound to the CachedProvider provider std::shared_ptr domain_scoped_client = api.GetClient("my-domain"); ``` ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ### Tracking The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. Note that some providers may not support tracking; check the documentation for your provider for more information. ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```cpp // shut down all providers openfeature::OpenFeatureAPI::GetInstance().Shutdown(); ``` ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/cpp-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. ```cpp #include "openfeature/provider.h" class MyProvider : public openfeature::FeatureProvider { public: openfeature::Metadata GetMetadata() const override { return {"My Provider"}; } absl::Status Init(const openfeature::EvaluationContext& ctx) override { // start up your provider return absl::OkStatus(); } absl::Status Shutdown() override { // shut down your provider return absl::OkStatus(); } std::unique_ptr GetBooleanEvaluation( std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) override { // resolve a boolean flag value } std::unique_ptr GetStringEvaluation( std::string_view flag, std::string_view default_value, const openfeature::EvaluationContext& ctx) override { // resolve a string flag value } std::unique_ptr GetIntegerEvaluation( std::string_view flag, int64_t default_value, const openfeature::EvaluationContext& ctx) override { // resolve a int flag value } std::unique_ptr GetDoubleEvaluation( std::string_view flag, double default_value, const openfeature::EvaluationContext& ctx) override { // resolve a double flag value } std::unique_ptr GetObjectEvaluation( std::string_view flag, Value default_value, const openfeature::EvaluationContext& ctx) override { // resolve a object flag value } }; ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/cpp-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the `Hook interface`. To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined. To avoid defining empty functions, make use of the `UnimplementedHook` struct (which already implements all the empty functions). > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature Dart SDK **Release status:** The server SDK has regular, non-beta releases on [pub.dev](https://pub.dev/packages/openfeature_dart_server_sdk). It remains pre-1.0; these releases do not establish a 1.0 API compatibility commitment. For typed provider event registration, late-handler behavior and compatibility with existing streams, see [the event migration guide](https://github.com/open-feature/dart-sdk/blob/main/packages/openfeature_dart_server_sdk/doc/events-migration.md). For reusable cleanup and experimental isolated API instances, see [shutdown and isolation](https://github.com/open-feature/dart-sdk/blob/main/packages/openfeature_dart_server_sdk/doc/shutdown-and-isolation.md). For experimental tracking and request context carriers, see [tracking and transactions](https://github.com/open-feature/dart-sdk/blob/main/packages/openfeature_dart_server_sdk/doc/tracking-and-transactions.md). Candidate requirement evidence and release gates are documented in the [conformance report guide](https://github.com/open-feature/dart-sdk/blob/development/doc/server-conformance-evidence.md). The candidate report does not declare full v0.9 conformance. New providers can implement a minimal resolver contract and opt into lifecycle, hooks and tracking. See the [provider migration guide](https://github.com/open-feature/dart-sdk/blob/main/packages/openfeature_dart_server_sdk/doc/provider-capabilities-migration.md) for the additive API and legacy lifecycle policy. Evaluation options now carry invocation hooks and immutable hints. See the [hooks migration guide](https://github.com/open-feature/dart-sdk/blob/main/packages/openfeature_dart_server_sdk/doc/hooks-migration.md) for typed stage callbacks, registration order and the compatibility decision for legacy priorities. [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.1.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/dart-sdk/releases/tag/v0.1.0)[![Built with Dart](https://img.shields.io/badge/Built%20with-Dart-blue.svg?style=for-the-badge)](https://dart.dev/)
[![Pub Version](https://img.shields.io/pub/v/openfeature_dart_server_sdk.svg?style=for-the-badge)](https://pub.dev/packages/openfeature_dart_server_sdk)[![API Reference](https://img.shields.io/badge/API-reference-blue.svg?style=for-the-badge)](https://openfeature.dev/docs/reference/intro)[![Code Coverage](https://codecov.io/gh/open-feature/dart-sdk/branch/main/graph/badge.svg?token=FZ17BHNSU5)](https://codecov.io/gh/open-feature/dart-sdk)[![GitHub CI Status](https://github.com/open-feature/dart-sdk/actions/workflows/validation-workflow.yml/badge.svg?style=for-the-badge)](https://github.com/open-feature/dart-sdk/actions/workflows/validation-workflow.yml) Immutable evaluation contexts are available as an explicit opt-in. Legacy context APIs retain their existing value and collection behavior. See the [context migration guide](https://github.com/open-feature/dart-sdk/blob/main/packages/openfeature_dart_server_sdk/doc/evaluation-context-migration.md) for snapshot semantics and the remaining conformance scope. ## Repository source and client SDK This server package now lives at `packages/openfeature_dart_server_sdk` in the source repository. Pub.dev dependencies and `package:openfeature_dart_server_sdk/...` imports are unchanged. Git dependencies that previously resolved the package from the repository root must add: ```yaml path: packages/openfeature_dart_server_sdk ``` The same repository also contains a separate pure-Dart client SDK for Dart VM, Dart web, and Flutter consumers. The two packages remain independently versioned and published. * See the [client package](https://github.com/open-feature/dart-sdk/tree/main/packages/openfeature_dart_client_sdk). * Read the [client SDK architecture](https://github.com/open-feature/dart-sdk/blob/main/doc/client-sdk-architecture.md). * Review the [client SDK conformance matrix](https://github.com/open-feature/dart-sdk/blob/main/doc/client-sdk-conformance-matrix.md). * Read the [client release procedure](https://github.com/open-feature/dart-sdk/blob/main/doc/client-sdk-release.md). * Follow [issue #117](https://github.com/open-feature/dart-sdk/issues/117) for implementation progress. ## Quick start ### Requirements Dart language version: [3.12.2](https://dart.dev/get-dart/archive) Note The OpenFeature Dart Server SDK only supports the latest currently maintained Dart language versions. ### Install ```yaml dependencies: openfeature_dart_server_sdk: ^0.1.0 ``` ### Then run ```text dart pub get ``` ### Usage ```dart import 'package:openfeature_dart_server_sdk/feature_provider.dart'; import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; void main() async { final api = OpenFeatureAPI(); await api.setProviderAndWait( InMemoryProvider({ 'new-feature': true, 'welcome-message': 'Hello, OpenFeature!', }), ); final client = api.getClient('my-app'); final newFeatureEnabled = await client.getBooleanFlag( 'new-feature', defaultValue: false, ); final details = await client.getBooleanDetails( 'new-feature', defaultValue: false, ); print('Reason: ${details.reason}, Variant: ${details.variant}'); if (newFeatureEnabled) { final welcomeMessage = await client.getStringFlag( 'welcome-message', defaultValue: 'Welcome!', ); print(welcomeMessage); } } ``` ### API reference See [pub.dev/documentation/openfeature_dart_server_sdk/latest](https://pub.dev/documentation/openfeature_dart_server_sdk/latest/) for the complete API documentation. ## Features | Status | Feature | Description | | ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations for experimentation. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ⚠️ | [Multi-Provider](#multi-provider-experimental) | Compose multiple providers behind one SDK-level provider with a selection strategy. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction such as an HTTP request or thread. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | Experimental: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. See the [available providers](//ecosystem?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5BallTechnologies%5D%5B0%5D=Dart) for a complete list. If the provider you need does not exist yet, see [Develop a provider](#develop-a-provider). ```dart final api = OpenFeatureAPI(); await api.setProviderAndWait(MyProvider()); ``` ### Targeting Sometimes the value of a flag must consider dynamic criteria about the application or user, such as location, IP, or organization. In OpenFeature this is called [targeting](https://openfeature.dev/specification/glossary.md#targeting). If your flag management system supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```dart final api = OpenFeatureAPI(); api.setGlobalContext( OpenFeatureEvaluationContext({ 'region': 'us-east-1-iah-1a', }), ); final client = FeatureClient( metadata: ClientMetadata(name: 'my-app'), hookManager: HookManager(), defaultContext: const EvaluationContext( attributes: { 'version': '1.4.6', }, ), provider: api.provider, ); final result = await client.getBooleanFlag( 'feature-flag', defaultValue: false, context: const EvaluationContext( attributes: { 'user': 'user-123', 'company': 'Initech', }, ), ); ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow custom logic to be added at well-defined points of the flag evaluation life-cycle. See the [available hooks](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Dart) for a complete list. Once you have added a hook dependency, it can be registered at the global or client level. ```dart final api = OpenFeatureAPI(); api.addHooks([MyGlobalHook()]); final client = api.getClient('my-app'); client.addHook(MyClientHook()); ``` Note Invocation-level hooks are not yet supported. Hooks can currently be registered at the global or client level. Before hooks may return context updates. Any returned attributes are merged into the evaluation context before the provider is called. ### Tracking The [tracking API](https://openfeature.dev/specification/sections/tracking.md) allows you to associate user actions with feature flag evaluations. This is useful for experimentation and downstream analytics. ```dart import 'package:openfeature_dart_server_sdk/evaluation_context.dart'; import 'package:openfeature_dart_server_sdk/feature_provider.dart'; import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; final api = OpenFeatureAPI(); final client = api.getClient('my-app'); await client.track( 'checkout-completed', context: const EvaluationContext( attributes: { 'user': 'user-123', }, ), trackingDetails: const TrackingEventDetails( value: 99.99, attributes: {'currency': 'USD'}, ), ); ``` Note that some providers may not support tracking. Check the provider documentation for details. ### Logging In accordance with the OpenFeature specification, the SDK does not generally log messages during flag evaluation. The SDK uses the [package:logging](https://pub.dev/packages/logging) structured logging API internally. You can configure log levels and listeners to capture SDK output for troubleshooting and debugging. For lifecycle-level logging, use the built-in `LoggingHook` or `OpenTelemetryHook`. `LoggingHook` redacts evaluation-context values by default and only emits context keys unless `includeContext: true` is set explicitly. ### Domains Clients can be assigned to a domain. A domain is a logical identifier that can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```dart import 'package:openfeature_dart_server_sdk/feature_provider.dart'; import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; final api = OpenFeatureAPI(); await api.setProviderAndWait(InMemoryProvider({'default-flag': true})); final cacheProviderId = await api.registerProviderAndWait( CustomCacheProvider(), ); await api.bindClientToProviderAndWait('cache-domain', cacheProviderId); final defaultClient = api.getClient('default-client'); await defaultClient.getBooleanFlag('default-flag', defaultValue: false); final cacheClient = api.getClient('cache-client', domain: 'cache-domain'); await cacheClient.getBooleanFlag('cached-flag', defaultValue: false); ``` ### Multi-Provider (experimental) OpenFeature treats Multi-Provider as an SDK-level utility rather than behavior owned by any one provider. It is useful for migrations and fallback compositions where one client should consult more than one underlying provider. This SDK includes an experimental `MultiProvider` implementation with a default `FirstMatchStrategy`. Providers are evaluated in order. A `FLAG_NOT_FOUND` result is treated as a miss and evaluation continues with the next provider. Initialization and connection fan out across all configured providers, and tracking is treated as best-effort fan-out. ```dart import 'package:openfeature_dart_server_sdk/feature_provider.dart'; import 'package:openfeature_dart_server_sdk/multi_provider.dart'; import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; final api = OpenFeatureAPI(); final multiProvider = MultiProvider([ InMemoryProvider({'new-feature': true}), InMemoryProvider({'fallback-feature': true}), ]); await api.setProviderAndWait(multiProvider); final client = api.getClient('my-app'); final enabled = await client.getBooleanFlag( 'new-feature', defaultValue: false, ); ``` Treat the current Multi-Provider surface as experimental until the strategy API and documentation settle further. ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events such as `PROVIDER_CONFIGURATION_CHANGED`. ```dart import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; import 'package:openfeature_dart_server_sdk/open_feature_event.dart'; final api = OpenFeatureAPI(); api.events.listen((event) { if (event.type == OpenFeatureEventType.PROVIDER_CONFIGURATION_CHANGED) { print('Provider configuration changed: ${event.message}'); } }); ``` Client-scoped handlers are also available. A client only receives events for its associated provider. ```dart final client = api.getClient('my-app'); final sub = client.addHandler((event) { print('Client received event: ${event.type}'); }); await client.removeHandler(sub); ``` The SDK also exposes a global event bus for SDK-specific flag evaluation events. ```dart import 'package:openfeature_dart_server_sdk/event_system.dart'; OpenFeatureEvents.instance.subscribe( (event) { print('Flag evaluated: ${event.data['flagKey']} = ${event.data['result']}'); }, filter: EventFilter( types: {OpenFeatureEventType.flagEvaluated}, ), ); ``` ### Shutdown The OpenFeature API provides mechanisms to perform cleanup of registered providers. This should only be called when your application is shutting down. ```dart import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; import 'package:openfeature_dart_server_sdk/shutdown_manager.dart'; final api = OpenFeatureAPI(); final shutdownManager = ShutdownManager(); shutdownManager.registerHook( ShutdownHook( name: 'provider-cleanup', phase: ShutdownPhase.PROVIDER_SHUTDOWN, execute: () async { await api.dispose(); }, ), ); await shutdownManager.shutdown(); ``` ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context, such as user id, user agent, or IP. Transaction context can be set where request-specific data is available and then automatically applied to flag evaluations inside that transaction. ```dart import 'package:openfeature_dart_server_sdk/transaction_context.dart'; final transactionManager = TransactionContextManager(); final context = TransactionContext( transactionId: 'request-123', attributes: { 'user': 'user-456', 'region': 'us-west-1', }, ); transactionManager.pushContext(context); await client.getBooleanFlag('my-flag', defaultValue: false); await transactionManager.withContext( 'transaction-id', {'user': 'user-123'}, () async { await client.getBooleanFlag('my-flag', defaultValue: false); }, ); transactionManager.popContext(); ``` ## Extending ### Develop a provider To develop a provider, create a new project and include the OpenFeature SDK as a dependency. This can live in a new repository or in the existing [contrib repository](https://github.com/open-feature/dart-server-sdk-contrib). ```dart import 'package:openfeature_dart_server_sdk/feature_provider.dart'; class MyCustomProvider implements FeatureProvider { @override String get name => 'MyCustomProvider'; @override ProviderMetadata get metadata => ProviderMetadata(name: name); @override ProviderState get state => ProviderState.READY; @override ProviderConfig get config => ProviderConfig(); @override Future initialize([Map? config]) async {} @override Future connect() async {} @override Future shutdown() async {} @override Future track( String trackingEventName, { Map? evaluationContext, TrackingEventDetails? trackingDetails, }) async {} @override Future> getBooleanFlag( String flagKey, bool defaultValue, { Map? context, }) async { return FlagEvaluationResult( flagKey: flagKey, value: true, evaluatedAt: DateTime.now(), evaluatorId: name, ); } @override Future> getStringFlag( String flagKey, String defaultValue, { Map? context, }) async { return FlagEvaluationResult( flagKey: flagKey, value: 'value', evaluatedAt: DateTime.now(), evaluatorId: name, ); } @override Future> getIntegerFlag( String flagKey, int defaultValue, { Map? context, }) async { return FlagEvaluationResult( flagKey: flagKey, value: 42, evaluatedAt: DateTime.now(), evaluatorId: name, ); } @override Future> getDoubleFlag( String flagKey, double defaultValue, { Map? context, }) async { return FlagEvaluationResult( flagKey: flagKey, value: 3.14, evaluatedAt: DateTime.now(), evaluatorId: name, ); } @override Future>> getObjectFlag( String flagKey, Map defaultValue, { Map? context, }) async { return FlagEvaluationResult( flagKey: flagKey, value: {'key': 'value'}, evaluatedAt: DateTime.now(), evaluatorId: name, ); } } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs. ### Develop a hook To develop a hook, create a new project and include the OpenFeature SDK as a dependency. This can live in a new repository or in the existing [contrib repository](https://github.com/open-feature/dart-server-sdk-contrib). Implement your own hook by using the hook interfaces exported by the SDK. ```dart import 'package:openfeature_dart_server_sdk/hooks.dart'; class MyCustomHook extends BaseHook { MyCustomHook() : super(metadata: HookMetadata(name: 'MyCustomHook')); @override Future?> before(HookContext context) async { print('Before evaluating flag: ${context.flagKey}'); return { 'requestSource': 'my-hook', }; } @override Future after(HookContext context) async { print('After evaluating flag: ${context.flagKey}, result: ${context.result}'); } @override Future error(HookContext context) async { print('Error evaluating flag: ${context.flagKey}, error: ${context.error}'); } @override Future finally_( HookContext context, EvaluationDetails? evaluationDetails, [ HookHints? hints, ]) async { print('Finished evaluating flag: ${context.flagKey}'); } } ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs. ## Testing Use the `InMemoryProvider` to set flags for the scope of a test. Use `OpenFeatureAPI.resetInstance()` in `tearDown` to clean up between tests. ```dart import 'package:openfeature_dart_server_sdk/feature_provider.dart'; import 'package:openfeature_dart_server_sdk/open_feature_api.dart'; import 'package:test/test.dart'; void main() { late OpenFeatureAPI api; late InMemoryProvider testProvider; setUp(() async { api = OpenFeatureAPI(); testProvider = InMemoryProvider({ 'test-flag': true, 'string-flag': 'test-value', }); await api.setProviderAndWait(testProvider); }); tearDown(() async { await OpenFeatureAPI.resetInstance(); }); test('evaluates boolean flag correctly', () async { final client = api.getClient('test-client'); final result = await client.getBooleanFlag( 'test-flag', defaultValue: false, ); expect(result, isTrue); }); test('evaluates string flag correctly', () async { final client = api.getClient('test-client'); final result = await client.getStringFlag( 'string-flag', defaultValue: 'default', ); expect(result, equals('test-value')); }); } ``` --- # OpenFeature .NET SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0) [![Release](https://img.shields.io/static/v1?label=release\&message=v2.14.1\&color=blue\&style=for-the-badge)](https://github.com/open-feature/dotnet-sdk/releases/tag/v2.14.1) [![Slack](https://img.shields.io/badge/slack-%40cncf%2Fopenfeature-brightgreen?style=flat\&logo=slack)](https://cloud-native.slack.com/archives/C0344AANLA1) [![Codecov](https://codecov.io/gh/open-feature/dotnet-sdk/branch/main/graph/badge.svg?token=MONAVJBXUJ)](https://codecov.io/gh/open-feature/dotnet-sdk) [![NuGet](https://img.shields.io/nuget/vpre/OpenFeature)](https://www.nuget.org/packages/OpenFeature) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6250/badge)](https://www.bestpractices.dev/en/projects/6250) ## Quick start ### Requirements * .NET 8+ * .NET Framework 4.6.2+ Note that the packages will aim to support all current .NET versions. Refer to the currently supported versions [.NET](https://dotnet.microsoft.com/download/dotnet) and [.NET Framework](https://dotnet.microsoft.com/download/dotnet-framework) excluding .NET Framework 3.5 ### NativeAOT Support ✅ **Full NativeAOT Compatibility** - The OpenFeature .NET SDK is fully compatible with .NET NativeAOT compilation for fast startup and small deployment size. See the [AOT Compatibility Guide](https://github.com/open-feature/dotnet-sdk/blob/main/docs/AOT_COMPATIBILITY.md) for detailed instructions. > While the core OpenFeature SDK is fully NativeAOT compatible, contrib and community-provided providers, hooks, and extensions may not be. Please check with individual provider/hook documentation for their NativeAOT compatibility status. ### Install Use the following to initialize your project: ```sh dotnet new console ``` and install OpenFeature: ```sh dotnet add package OpenFeature ``` ### Usage ```csharp public async Task Example() { // Register your feature flag provider try { await Api.Instance.SetProviderAsync(new InMemoryProvider()); } catch (Exception ex) { // Log error } // Create a new client FeatureClient client = Api.Instance.GetClient(); // Evaluate your feature flag bool v2Enabled = await client.GetBooleanValueAsync("v2_enabled", false); if ( v2Enabled ) { // Do some work } } ``` ### Samples The [`samples/`](https://github.com/open-feature/dotnet-sdk/blob/main/samples) folder contains example applications demonstrating how to use OpenFeature in different .NET scenarios. | Sample Name | Description | | ----------------------------------------------------------------------------------------------- | ----------------------------------------- | | [AspNetCore](https://github.com/open-feature/dotnet-sdk/blob/main/samples/AspNetCore/README.md) | Feature flags in an ASP.NET Core Web API. | | [Console](https://github.com/open-feature/dotnet-sdk/blob/main/samples/Console/README.md) | Feature flags in a .NET console app. | **Getting Started with a Sample:** 1. Navigate to the sample directory ```shell cd samples/AspNetCore ``` 2. Restore dependencies and run ```shell dotnet run ``` Want to contribute a new sample? See our [CONTRIBUTING](https://github.com/open-feature/dotnet-sdk/blob/main/CONTRIBUTING.md) guide! ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread). | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | | 🔬 | [Multi-Provider](#multi-provider) | Use multiple feature flag providers simultaneously with configurable evaluation strategies. | | 🔬 | [DependencyInjection](#dependency-injection) | Integrate OpenFeature with .NET's dependency injection for streamlined provider setup. | > Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ | Experimental: 🔬 ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Here is [a complete list of available providers](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=.NET). If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```csharp try { await Api.Instance.SetProviderAsync(new MyProvider()); } catch (Exception ex) { // Log error } ``` When calling `SetProviderAsync` an exception may be thrown if the provider cannot be initialized. This may occur if the provider has not been configured correctly. See the documentation for the provider you are using for more information on how to configure the provider correctly. In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```csharp // set a value to the global context EvaluationContextBuilder builder = EvaluationContext.Builder(); builder.Set("region", "us-east-1"); EvaluationContext apiCtx = builder.Build(); Api.Instance.SetContext(apiCtx); // set a value to the client context builder = EvaluationContext.Builder(); builder.Set("region", "us-east-1"); EvaluationContext clientCtx = builder.Build(); var client = Api.Instance.GetClient(); client.SetContext(clientCtx); // set a value to the invocation context builder = EvaluationContext.Builder(); builder.Set("region", "us-east-1"); EvaluationContext reqCtx = builder.Build(); bool flagValue = await client.GetBooleanValueAsync("some-flag", false, reqCtx); ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Server-side\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=.NET) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```csharp // add a hook globally, to run on all evaluations Api.Instance.AddHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client var client = Api.Instance.GetClient(); client.AddHooks(new ExampleClientHook()); // add a hook for this evaluation only var value = await client.GetBooleanValueAsync("boolFlag", false, context, new FlagEvaluationOptions(new ExampleInvocationHook())); ``` ### Logging The .NET SDK uses Microsoft.Extensions.Logging. See the [manual](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line) for complete documentation. Note that in accordance with the OpenFeature specification, the SDK doesn't generally log messages during flag evaluation. If you need further troubleshooting, please look into the `Logging Hook` section. #### Logging Hook The .NET SDK includes a LoggingHook, which logs detailed information at key points during flag evaluation, using Microsoft.Extensions.Logging structured logging API. This hook can be particularly helpful for troubleshooting and debugging; simply attach it at the global, client or invocation level and ensure your log level is set to "debug". ```csharp using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); var logger = loggerFactory.CreateLogger("Program"); var client = Api.Instance.GetClient(); client.AddHooks(new LoggingHook(logger)); ``` See [hooks](#hooks) for more information on configuring hooks. ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```csharp try { // registering the default provider await Api.Instance.SetProviderAsync(new LocalProvider()); // registering a provider to a domain await Api.Instance.SetProviderAsync("clientForCache", new CachedProvider()); } catch (Exception ex) { // Log error } // a client backed by default provider FeatureClient clientDefault = Api.Instance.GetClient(); // a client backed by CachedProvider FeatureClient scopedClient = Api.Instance.GetClient("clientForCache"); ``` Domains can be defined on a provider during registration. For more details, please refer to the [providers](#providers) section. ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. Example usage of an Event handler: ```csharp public static void EventHandler(ProviderEventPayload eventDetails) { Console.WriteLine(eventDetails.Type); } ``` ```csharp EventHandlerDelegate callback = EventHandler; // add an implementation of the EventHandlerDelegate for the PROVIDER_READY event Api.Instance.AddHandler(ProviderEventTypes.ProviderReady, callback); ``` It is also possible to register an event handler for a specific client, as in the following example: ```csharp EventHandlerDelegate callback = EventHandler; var myClient = Api.Instance.GetClient("my-client"); try { var provider = new ExampleProvider(); await Api.Instance.SetProviderAsync(myClient.GetMetadata().Name, provider); } catch (Exception ex) { // Log error } myClient.AddHandler(ProviderEventTypes.ProviderReady, callback); ``` ### Tracking The [tracking API](https://openfeature.dev/specification/sections/tracking.md) allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a hook(#hooks) or provider(#providers) can be associated with telemetry reported in the client's `track` function. ```csharp var client = Api.Instance.GetClient(); client.Track("visited-promo-page", trackingEventDetails: TrackingEventDetails.Builder().SetValue(99.77).Set("currency", "USD").Build()); ``` Note that some providers may not support tracking; check the documentation for your provider for more information. ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```csharp // Shut down all providers await Api.Instance.ShutdownAsync(); ``` ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). By default, the `NoOpTransactionContextPropagator` is used, which doesn't store anything. To register a [AsyncLocal](https://learn.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1) context propagator, you can use the `SetTransactionContextPropagator` method as shown below. ```csharp // registering the AsyncLocalTransactionContextPropagator Api.Instance.SetTransactionContextPropagator(new AsyncLocalTransactionContextPropagator()); ``` Once you've registered a transaction context propagator, you can propagate the data into request-scoped transaction context. ```csharp // adding userId to transaction context EvaluationContext transactionContext = EvaluationContext.Builder() .Set("userId", userId) .Build(); Api.Instance.SetTransactionContext(transactionContext); ``` Additionally, you can develop a custom transaction context propagator by implementing the `TransactionContextPropagator` interface and registering it as shown above. ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/dotnet-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. ```csharp public class MyProvider : FeatureProvider { public override Metadata GetMetadata() { return new Metadata("My Provider"); } public override Task> ResolveBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) { // resolve a boolean flag value } public override Task> ResolveStringValueAsync(string flagKey, string defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) { // resolve a string flag value } public override Task> ResolveIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) { // resolve an int flag value } public override Task> ResolveDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) { // resolve a double flag value } public override Task> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) { // resolve an object flag value } } ``` ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/dotnet-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the `Hook interface`. To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined. ```csharp public class MyHook : Hook { public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) { // code to run before flag evaluation } public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) { // code to run after successful flag evaluation } public override ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) { // code to run if there's an error during before hooks or during flag evaluation } public override ValueTask FinallyAsync(HookContext context, FlagEvaluationDetails evaluationDetails, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) { // code to run after all other stages, regardless of success/failure } } ``` Hooks support passing per-evaluation data between that stages using `hook data`. The below example hook uses `hook data` to measure the duration between the execution of the `before` and `after` stage. ```csharp class TimingHook : Hook { public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) { context.Data.Set("beforeTime", DateTime.Now); return ValueTask.FromResult(context.EvaluationContext); } public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) { var beforeTime = context.Data.Get("beforeTime") as DateTime?; var duration = DateTime.Now - beforeTime; Console.WriteLine($"Duration: {duration}"); return ValueTask.CompletedTask; } } ``` Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! ### Multi-Provider Note The Multi-Provider feature is currently experimental. The Multi-Provider enables the use of multiple underlying feature flag providers simultaneously, allowing different providers to be used for different flag keys or based on specific evaluation strategies. The Multi-Provider supports provider hooks and executes them in accordance with the OpenFeature specification. Each provider's hooks are executed with context isolation, ensuring that context modifications by one provider's hooks do not affect other providers. #### Basic Usage ```csharp using OpenFeature.Providers.MultiProvider; using OpenFeature.Providers.MultiProvider.Models; using OpenFeature.Providers.MultiProvider.Strategies; // Create provider entries var providerEntries = new List { new(new InMemoryProvider(provider1Flags), "Provider1"), new(new InMemoryProvider(provider2Flags), "Provider2") }; // Create multi-provider with FirstMatchStrategy (default) var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy()); // Set as the default provider await Api.Instance.SetProviderAsync(multiProvider); // Use normally - the multi-provider will handle delegation var client = Api.Instance.GetClient(); var flagValue = await client.GetBooleanValueAsync("my-flag", false); ``` #### Evaluation Strategies The Multi-Provider supports different evaluation strategies that determine how multiple providers are used: ##### FirstMatchStrategy (Default) Evaluates providers sequentially and returns the first result that is not "flag not found". If any provider returns an error, that error is returned immediately. ```csharp var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy()); ``` ##### FirstSuccessfulStrategy Evaluates providers sequentially and returns the first successful result, ignoring errors. Only if all providers fail will errors be returned. ```csharp var multiProvider = new MultiProvider(providerEntries, new FirstSuccessfulStrategy()); ``` ##### ComparisonStrategy Evaluates all providers in parallel and compares results. If values agree, returns the agreed value. If they disagree, returns the fallback provider's value (or first provider if no fallback is specified) and optionally calls a mismatch callback. ```csharp // Basic comparison var multiProvider = new MultiProvider(providerEntries, new ComparisonStrategy()); // With fallback provider var multiProvider = new MultiProvider(providerEntries, new ComparisonStrategy(fallbackProvider: provider1)); // With mismatch callback var multiProvider = new MultiProvider(providerEntries, new ComparisonStrategy(onMismatch: (mismatchDetails) => { // Log or handle mismatches between providers foreach (var kvp in mismatchDetails) { Console.WriteLine($"Provider {kvp.Key}: {kvp.Value}"); } })); ``` #### Evaluation Modes The Multi-Provider supports two evaluation modes: * **Sequential**: Providers are evaluated one after another (used by `FirstMatchStrategy` and `FirstSuccessfulStrategy`) * **Parallel**: All providers are evaluated simultaneously (used by `ComparisonStrategy`) #### Limitations * **Experimental status**: The API may change in future releases For a complete example, see the [AspNetCore sample](https://github.com/open-feature/dotnet-sdk/blob/main/samples/AspNetCore/README.md) which demonstrates Multi-Provider usage. ### Dependency Injection Note The OpenFeature.Hosting package is currently experimental. The Hosting package streamlines the integration of OpenFeature within .NET applications, allowing for seamless configuration and lifecycle management of feature flag providers using dependency injection and hosting services. #### Installation To set up dependency injection and hosting capabilities for OpenFeature, install the following package: ```sh dotnet add package OpenFeature.Hosting ``` #### Usage Examples For a basic configuration, you can use the InMemoryProvider. This provider is simple and well-suited for development and testing purposes. **Basic Configuration:** ```csharp builder.Services.AddOpenFeature(featureBuilder => { featureBuilder .AddInMemoryProvider(); }); ``` You can add EvaluationContext, hooks, and handlers at a global/API level as needed. ```csharp builder.Services.AddOpenFeature(featureBuilder => { featureBuilder .AddContext((contextBuilder, serviceProvider) => { /* Custom context configuration */ }) .AddHook() .AddHandler(ProviderEventTypes.ProviderReady, (eventDetails) => { /* Handle event */ }); }); ``` **Domain-Scoped Provider Configuration:**
To set up multiple providers with a selection policy, define logic for choosing the default provider. This example designates `name1` as the default provider: ```csharp builder.Services.AddOpenFeature(featureBuilder => { featureBuilder .AddContext((contextBuilder, serviceProvider) => { /* Custom context configuration */ }) .AddHook((serviceProvider) => new LoggingHook( /* Custom configuration */ )) .AddHook(new MetricsHook()) .AddInMemoryProvider("name1") .AddInMemoryProvider("name2") .AddPolicyName(options => { // Custom logic to select a default provider options.DefaultNameSelector = serviceProvider => "name1"; }); }); ``` ### Registering a Custom Provider You can register a custom provider, such as `InMemoryProvider`, with OpenFeature using the `AddProvider` method. This approach allows you to dynamically resolve services or configurations during registration. ```csharp services.AddOpenFeature(builder => { builder.AddProvider(provider => { // Resolve services or configurations as needed var variants = new Dictionary { { "on", true } }; var flags = new Dictionary { { "feature-key", new Flag(variants, "on") } }; // Register a custom provider, such as InMemoryProvider return new InMemoryProvider(flags); }); }); ``` #### Adding a Domain-Scoped Provider You can also register a domain-scoped custom provider, enabling configurations specific to each domain: ```csharp services.AddOpenFeature(builder => { builder.AddProvider("my-domain", (provider, domain) => { // Resolve services or configurations as needed for the domain var variants = new Dictionary { { "on", true } }; var flags = new Dictionary { { $"{domain}-feature-key", new Flag(variants, "on") } }; // Register a domain-scoped custom provider such as InMemoryProvider return new InMemoryProvider(flags); }); }); ``` ### Trace Enricher Hook Warning As of v2.14, the OpenFeature SDK emits native OpenTelemetry spans for every flag evaluation without requiring any hooks. Use `TraceEnricherHook` when you want activities/spans **outside** of OpenFeature to be enriched with flag evaluation data. If you are already using `TraceEnricherHook`, you may see **new spans** appear in your observability platform after upgrading to v2.14. The `TraceEnricherHook` enriches telemetry traces with additional information during the feature flag evaluation lifecycle. This hook adds relevant flag evaluation details as tags and events to the current `Activity` for tracing purposes. For this hook to function correctly, an active span must be set in the current `Activity`, otherwise the hook will no-op. Below are the tags added to the trace event: | Tag Name | Description | Source | | --------------------------- | ---------------------------------------------------------------------------- | ----------------------------- | | feature_flag.key | The lookup key of the feature flag | Hook context flag key | | feature_flag.provider.name | The name of the feature flag provider | Provider metadata | | feature_flag.result.reason | The reason code which shows how a feature flag value was determined | Evaluation details | | feature_flag.result.variant | A semantic identifier for an evaluated flag value | Evaluation details | | feature_flag.result.value | The evaluated value of the feature flag | Evaluation details | | feature_flag.context.id | The unique identifier for the flag evaluation context | Flag metadata (if available) | | feature_flag.set.id | The identifier of the flag set to which the feature flag belongs | Flag metadata (if available) | | feature_flag.version | The version of the ruleset used during the evaluation | Flag metadata (if available) | | error.type | Describes a class of error the operation ended with | Evaluation details (if error) | | error.message | A message explaining the nature of an error occurring during flag evaluation | Evaluation details (if error) | Note To receive OpenTelemetry traces emitted natively by the OpenFeature SDK, add `.AddSource("OpenFeature")` to your `TracerProviderBuilder`. No hooks are required. #### Example The following example demonstrates the use of the `TraceEnricherHook` with the `OpenFeature dotnet-sdk`. The traces are sent to a `jaeger` OTLP collector running at `localhost:4317`. ```csharp using System.Threading.Tasks; using OpenFeature.Contrib.Providers.Flagd; using OpenFeature.Hooks; using OpenTelemetry.Exporter; using OpenTelemetry.Resources; using OpenTelemetry; using OpenTelemetry.Trace; namespace OpenFeatureTestApp { class Hello { static async Task Main(string[] args) { // set up the OpenTelemetry OTLP exporter var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("OpenFeature") // receive traces from OpenFeature SDK .AddSource("my-tracer") .ConfigureResource(r => r.AddService("jaeger-test")) .AddOtlpExporter(o => { o.ExportProcessorType = ExportProcessorType.Simple; }) .Build(); // add the TraceEnricherHook to the OpenFeature instance OpenFeature.Api.Instance.AddHooks(new TraceEnricherHook()); var flagdProvider = new FlagdProvider(new Uri("http://localhost:8013")); // Set the flagdProvider as the provider for the OpenFeature SDK await OpenFeature.Api.Instance.SetProviderAsync(flagdProvider); var client = OpenFeature.Api.Instance.GetClient("my-app"); var val = await client.GetBooleanValueAsync("myBoolFlag", false); // Print the value of the 'myBoolFlag' feature flag System.Console.WriteLine(val); } } } ``` After running this example, you will be able to see the traces, including the events sent by the hook in your Jaeger UI. You can specify custom tags on spans created by the `TraceEnricherHook` by providing `TraceEnricherHookOptions` when adding the hook: ```csharp var options = TraceEnricherHookOptions.CreateBuilder() .WithTag("custom_dimension_key", "custom_dimension_value") .Build(); OpenFeature.Api.Instance.AddHooks(new TraceEnricherHook(options)); ``` You can also write your own extraction logic against the Flag metadata by providing a callback to `WithFlagEvaluationMetadata`. The below example will add a tag to the span with the key `boolean` and a value specified by the callback. ```csharp var options = TraceEnricherHookOptions.CreateBuilder() .WithFlagEvaluationMetadata("boolean", s => s.GetBool("boolean")) .Build(); ``` ### Metrics Hook For this hook to function correctly a global `MeterProvider` must be set. `MetricsHook` performs metric collection by tapping into various hook stages. Below are the metrics extracted by this hook and dimensions they carry: | Metric key | Description | Unit | Dimensions | | -------------------------------------- | ------------------------------- | ---------- | ----------------------------- | | feature_flag.evaluation_requests_total | Number of evaluation requests | request | key, provider name | | feature_flag.evaluation_success_total | Flag evaluation successes | impression | key, provider name, reason | | feature_flag.evaluation_error_total | Flag evaluation errors | 1 | key, provider name, exception | | feature_flag.evaluation_active_count | Active flag evaluations counter | 1 | key, provider name | Consider the following code example for usage. #### Example The following example demonstrates the use of the `MetricsHook` with the `OpenFeature dotnet-sdk`. The metrics are sent to the `console`. ```csharp using System.Threading.Tasks; using OpenFeature.Contrib.Providers.Flagd; using OpenFeature; using OpenFeature.Hooks; using OpenTelemetry; using OpenTelemetry.Metrics; namespace OpenFeatureTestApp { class Hello { static async Task Main(string[] args) { // set up the OpenTelemetry OTLP exporter var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter("OpenFeature") .ConfigureResource(r => r.AddService("openfeature-test")) .AddConsoleExporter() .Build(); // add the MetricsHook to the OpenFeature instance OpenFeature.Api.Instance.AddHooks(new MetricsHook()); var flagdProvider = new FlagdProvider(new Uri("http://localhost:8013")); // Set the flagdProvider as the provider for the OpenFeature SDK await OpenFeature.Api.Instance.SetProviderAsync(flagdProvider); var client = OpenFeature.Api.Instance.GetClient("my-app"); var val = await client.GetBooleanValueAsync("myBoolFlag", false); // Print the value of the 'myBoolFlag' feature flag System.Console.WriteLine(val); } } } ``` After running this example, you should be able to see some metrics being generated into the console. You can specify custom dimensions on all instruments by the `MetricsHook` by providing `MetricsHookOptions` when adding the hook: ```csharp var options = MetricsHookOptions.CreateBuilder() .WithCustomDimension("custom_dimension_key", "custom_dimension_value") .Build(); OpenFeature.Api.Instance.AddHooks(new MetricsHook(options)); ``` You can also write your own extraction logic against the Flag metadata by providing a callback to `WithFlagEvaluationMetadata`. ```csharp var options = MetricsHookOptions.CreateBuilder() .WithFlagEvaluationMetadata("boolean", s => s.GetBool("boolean")) .Build(); ``` --- # OpenFeature Go SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.7.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.7.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v1.19.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/go-sdk/releases/tag/v1.19.0)
[![API Reference](https://pkg.go.dev/badge/github.com/open-feature/go-sdk/openfeature.svg)](https://pkg.go.dev/github.com/open-feature/go-sdk/openfeature)[![Go Report Card](https://goreportcard.com/badge/github.com/open-feature/go-sdk)](https://goreportcard.com/report/github.com/open-feature/go-sdk)[![codecov](https://codecov.io/gh/open-feature/go-sdk/branch/main/graph/badge.svg?token=FZ17BHNSU5)](https://codecov.io/gh/open-feature/go-sdk)[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6601/badge)](https://bestpractices.coreinfrastructure.org/projects/6601) ## Quick start ### Requirements Go language version: [1.26](https://go.dev/doc/devel/release#go1.26.0) Note The OpenFeature Go SDK only supports currently maintained Go language versions. ### Install ```shell go get github.com/open-feature/go-sdk ``` ### Usage ```go package main import ( "fmt" "context" "github.com/open-feature/go-sdk/openfeature" ) func main() { // Register your feature flag provider openfeature.SetProviderAndWait(openfeature.NoopProvider{}) // Create a new client client := openfeature.NewDefaultClient() // Evaluate your feature flag v2Enabled := client.Boolean( context.TODO(), "v2_enabled", true, openfeature.EvaluationContext{}, ) // Use the returned flag value if v2Enabled { fmt.Println("v2 is enabled") } } ``` Try this example in the [Go Playground](https://go.dev/play/p/fSSK8s42hA2). ### API Reference See [here](https://pkg.go.dev/github.com/open-feature/go-sdk/openfeature) for the complete API documentation. ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread) | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Go) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```go openfeature.SetProviderAndWait(MyProvider{}) ``` In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more details below, or the included [multiprovider](#multi-provider-implementation) implementation. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```go // set a value to the global context openfeature.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map[string]any{ "region": "us-east-1-iah-1a", }, )) // set a value to the client context client := openfeature.NewDefaultClient() client.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map[string]any{ "version": "1.4.6", }, )) // set a value to the invocation context evalCtx := openfeature.NewEvaluationContext( "user-123", map[string]any{ "company": "Initech", }, ) boolValue := client.Boolean(context.TODO(), "boolFlag", false, evalCtx) ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Go) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```go // add a hook globally, to run on all evaluations openfeature.AddHooks(ExampleGlobalHook{}) // add a hook on this client, to run on all evaluations made by this client client := openfeature.NewDefaultClient() client.AddHooks(ExampleClientHook{}) // add a hook for this evaluation only value := client.Boolean( context.TODO(), "boolFlag", false, openfeature.EvaluationContext{}, openfeature.WithHooks(ExampleInvocationHook{}), ) ``` ### Tracking The [tracking API](https://openfeature.dev/specification/sections/tracking.md) allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. ```go // initialize a client client := openfeature.NewDefaultClient() // trigger tracking event action client.Track( context.TODO(), "visited-promo-page", openfeature.EvaluationContext{}, openfeature.NewTrackingEventDetails(99.77).Add("currencyCode", "USD"), ) ``` Note that some providers may not support tracking; check the documentation for your provider for more information. ### Logging Note that in accordance with the OpenFeature specification, the SDK doesn't generally log messages during flag evaluation. #### Logging Hook The GO SDK includes a `LoggingHook`, which logs detailed information at key points during flag evaluation, using [slog](https://pkg.go.dev/log/slog) structured logging API. This hook can be particularly helpful for troubleshooting and debugging; simply attach it at the global, client or invocation level and ensure your log level is set to "debug". ##### Usage example ```go package main import ( "context" "log/slog" "os" "github.com/open-feature/go-sdk/openfeature" "github.com/open-feature/go-sdk/openfeature/hooks" "github.com/open-feature/go-sdk/openfeature/memprovider" ) func main() { // Register an in-memory provider with no flags openfeature.SetNamedProviderAndWait("example", memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{})) // Configure slog handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}) logger := slog.New(handler) // Register a logging hook globally to run on all evaluations loggingHook := hooks.NewLoggingHook(false, logger) openfeature.AddHooks(loggingHook) // Create a new client client := openfeature.NewClient("example") // Attempt to evaluate a flag that doesn't exist _ = client.Boolean(context.TODO(), "not-exist", true, openfeature.EvaluationContext{}) } ``` ###### Output ```sh {"time":"2025-06-03T10:49:23.100783-04:00","level":"DEBUG","msg":"Before stage","domain":"example","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true,"stage":"before"} {"time":"2025-06-03T10:49:23.101037-04:00","level":"ERROR","msg":"Error stage","domain":"example","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true,"error_message":"error code: FLAG_NOT_FOUND: flag for key not-exist not found","stage":"error"} ``` See [hooks](#hooks) for more information on configuring hooks. ### Domains Clients can be assigned to a domain. A domain is a logical identifier that can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```go import ( "github.com/open-feature/go-sdk/openfeature" "github.com/open-feature/go-sdk/openfeature/memprovider" ) // Registering the default provider openfeature.SetProviderAndWait(openfeature.NoopProvider{}) // Registering a named provider openfeature.SetNamedProvider("clientForCache", memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{})) // A Client backed by default provider clientWithDefault := openfeature.NewDefaultClient() // A Client backed by NewCachedProvider clientForCache := openfeature.NewClient("clientForCache") ``` ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ```go import "github.com/open-feature/go-sdk/openfeature" ... var readyHandlerCallback = func(details openfeature.EventDetails) { // callback implementation } // Global event handler openfeature.AddHandler(openfeature.ProviderReady, &readyHandlerCallback) ... var providerErrorCallback = func(details openfeature.EventDetails) { // callback implementation } client := openfeature.NewDefaultClient() // Client event handler client.AddHandler(openfeature.ProviderError, &providerErrorCallback) ``` ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```go import "github.com/open-feature/go-sdk/openfeature" openfeature.Shutdown() ``` ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler), and by using the transaction context propagator, it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). ```go import "github.com/open-feature/go-sdk/openfeature" // set the TransactionContext ctx := openfeature.WithTransactionContext(context.TODO(), openfeature.EvaluationContext{}) // get the TransactionContext from a context ec := openfeature.TransactionContext(ctx) // merge an EvaluationContext with the existing TransactionContext, preferring // the context that is passed to MergeTransactionContext tCtx := openfeature.MergeTransactionContext(ctx, openfeature.EvaluationContext{}) // use TransactionContext in a flag evaluation _ = client.Boolean(tCtx, ....) ``` ### Multi-Provider Implementation Included with this SDK is an *experimental* multi-provider that can be used to query multiple feature flag providers simultaneously. More information can be found in the [multi package's README](https://github.com/open-feature/go-sdk/blob/main/openfeature/multi/README.md). ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/go-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. ```go package myfeatureprovider import ( "context" "github.com/open-feature/go-sdk/openfeature" ) // MyFeatureProvider implements the FeatureProvider interface and provides functions for evaluating flags type MyFeatureProvider struct{} // Required: Methods below implements openfeature.FeatureProvider interface // This is the core interface implementation required from a provider // Metadata returns the metadata of the provider func (i MyFeatureProvider) Metadata() openfeature.Metadata { return openfeature.Metadata{ Name: "MyFeatureProvider", } } // Hooks returns a collection of openfeature.Hook defined by this provider func (i MyFeatureProvider) Hooks() []openfeature.Hook { // Hooks that should be included with the provider return []openfeature.Hook{} } // BooleanEvaluation returns a boolean flag func (i MyFeatureProvider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, flatCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail { // code to evaluate boolean } // StringEvaluation returns a string flag func (i MyFeatureProvider) StringEvaluation(ctx context.Context, flag string, defaultValue string, flatCtx openfeature.FlattenedContext) openfeature.StringResolutionDetail { // code to evaluate string } // FloatEvaluation returns a float flag func (i MyFeatureProvider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64, flatCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail { // code to evaluate float } // IntEvaluation returns an int flag func (i MyFeatureProvider) IntEvaluation(ctx context.Context, flag string, defaultValue int64, flatCtx openfeature.FlattenedContext) openfeature.IntResolutionDetail { // code to evaluate int } // ObjectEvaluation returns an object flag func (i MyFeatureProvider) ObjectEvaluation(ctx context.Context, flag string, defaultValue any, flatCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail { // code to evaluate object } // Optional: openfeature.StateHandler implementation // Providers can opt-in for initialization & shutdown behavior by implementing this interface // Init holds initialization logic of the provider func (i MyFeatureProvider) Init(evaluationContext openfeature.EvaluationContext) error { // code to initialize your provider } // Shutdown define the shutdown operation of the provider func (i MyFeatureProvider) Shutdown() { // code to shutdown your provider } // Optional: openfeature.EventHandler implementation. // Providers can opt-in for eventing support by implementing this interface // EventChannel returns the event channel of this provider func (i MyFeatureProvider) EventChannel() <-chan openfeature.Event { // expose event channel from this provider. SDK listen to this channel and invoke event handlers } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/go-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the [Hook interface](https://github.com/open-feature/go-sdk/blob/main/openfeature/hooks.go). To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined. To avoid defining empty functions make use of the `UnimplementedHook` struct (which already implements all the empty functions). ```go import ( "context" "github.com/open-feature/go-sdk/openfeature" ) type MyHook struct { openfeature.UnimplementedHook } // overrides UnimplementedHook's Error function func (h MyHook) Error(context context.Context, hookContext openfeature.HookContext, err error, hookHints openfeature.HookHints) { // code that runs when there's an error during a flag evaluation } ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! ## Testing The SDK provides a `NewTestProvider` which allows you to set flags for the scope of a test. The `TestProvider` is thread-safe and can be used in tests that run in parallel. Call `testProvider.UsingFlags(t, tt.flags)` to set flags for a test, and clean them up with `testProvider.Cleanup()` ```go import ( "github.com/open-feature/go-sdk/openfeature" "github.com/open-feature/go-sdk/openfeature/testing" ) testProvider := testing.NewTestProvider() err := openfeature.SetProviderAndWait(testProvider) if err != nil { t.Errorf("unable to set provider") } // configure flags for this test suite tests := map[string]struct { flags map[string]memprovider.InMemoryFlag want bool }{ "test when flag is true": { flags: map[string]memprovider.InMemoryFlag{ "my_flag": { State: memprovider.Enabled, DefaultVariant: "on", Variants: map[string]any{ "on": true, }, }, }, want: true, }, "test when flag is false": { flags: map[string]memprovider.InMemoryFlag{ "my_flag": { State: memprovider.Enabled, DefaultVariant: "off", Variants: map[string]any{ "off": false, }, }, }, want: false, }, } for name, tt := range tests { tt := tt name := name t.Run(name, func(t *testing.T) { // be sure to clean up your flags defer testProvider.Cleanup() testProvider.UsingFlags(t, tt.flags) // your code under test got := functionUnderTest() if got != tt.want { t.Fatalf("uh oh, value is not as expected: got %v, want %v", got, tt.want) } }) } ``` If your test code runs in a different goroutine, `TestProvider.UsingFlags` returns a context that should be used for evaluations. You can pass `*testing.T` directly. ```go // In your test, 't' is a *testing.T ctx := testProvider.UsingFlags(t, tt.flags) go func() { // Make sure to use the context returned by UsingFlags in the new goroutine. // The context carries the necessary information for the TestProvider. _ = openfeature.NewDefaultClient().Boolean(ctx, "my_flag", false, openfeature.EvaluationContext{}) }() ``` ### Mocks Mocks are also available for testing purposes for all interfaces within the OpenFeature SDK. These are primarily intended for internal use for testing the SDK, but have been exported to ease the testing burden for any extensions or custom components (e.g. hooks & providers). These mocks are not include in builds by default. The build tag `testtools` must be used to have the mocks included in builds. --- # OpenFeature Java SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.7.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.7.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v1.23.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/java-sdk/releases/tag/v1.23.0)
[![Javadoc](https://javadoc.io/badge2/dev.openfeature/sdk/javadoc.svg)](https://javadoc.io/doc/dev.openfeature/sdk)[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.openfeature/sdk/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.openfeature/sdk)[![Codecov](https://codecov.io/gh/open-feature/java-sdk/branch/main/graph/badge.svg?token=XMS9L7PBY1)](https://codecov.io/gh/open-feature/java-sdk)[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6241/badge)](https://bestpractices.coreinfrastructure.org/projects/6241) ## Quick start ### Requirements * Java 11+ (compiler target is 11) Note that this library is intended to be used in server-side contexts and has not been evaluated for use on mobile devices. ### Install #### Maven ```xml dev.openfeature sdk 1.23.0 ``` If you would like snapshot builds, this is the relevant repository information: ```xml true sonatype Sonatype Repository https://s01.oss.sonatype.org/content/repositories/snapshots/ ``` #### Gradle ```groovy dependencies { implementation 'dev.openfeature:sdk:1.23.0' } ``` ### Usage ```java public void example(){ // flags defined in memory Map> myFlags = new HashMap<>(); myFlags.put("v2_enabled", Flag.builder() .variant("on", true) .variant("off", false) .defaultVariant("on") .build()); // configure a provider OpenFeatureAPI api = OpenFeatureAPI.getInstance(); try { api.setProviderAndWait(new InMemoryProvider(myFlags)); } catch (Exception e) { // handle initialization failure e.printStackTrace(); } // create a client Client client = api.getClient(); // get a bool flag value boolean flagValue = client.getBooleanValue("v2_enabled", false); } ``` ### API Reference See [here](https://javadoc.io/doc/dev.openfeature/sdk/latest/) for the Javadocs. ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Multi-provider (experimental)](#multi-provider-experimental) | Combine multiple providers and delegate evaluations according to a strategy. | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread). | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Java) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. #### Multi-provider (experimental) In addition to domains, you may want to delegate flag evaluation across multiple providers using a configurable strategy. The multi-provider allows you to compose several `FeatureProvider` implementations and determine which provider's result to use. > **Experimental:** This API is experimental and may change in future releases. ```java import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.multiprovider.MultiProvider; import java.util.List; public void multiProviderExample() throws Exception { FeatureProvider primaryProvider = new MyPrimaryProvider(); FeatureProvider fallbackProvider = new MyFallbackProvider(); MultiProvider multiProvider = new MultiProvider(List.of(primaryProvider, fallbackProvider)); OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.setProviderAndWait(multiProvider); Client client = api.getClient(); boolean value = client.getBooleanValue("some-flag", false); } ``` #### Synchronous To register a provider in a blocking manner to ensure it is ready before further actions are taken, you can use the `setProviderAndWait` method as shown below: ```java OpenFeatureAPI api = OpenFeatureAPI.getInstance(); try { api.setProviderAndWait(new MyProvider()); } catch (Exception e) { // handle initialization failure e.printStackTrace(); } ``` #### Asynchronous To register a provider in a non-blocking manner, you can use the `setProvider` method as shown below: ```java OpenFeatureAPI.getInstance().setProvider(new MyProvider()); ``` In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```java // set a value to the global context OpenFeatureAPI api = OpenFeatureAPI.getInstance(); Map apiAttrs = new HashMap<>(); apiAttrs.put("region", new Value(System.getenv("AWS_REGION"))); EvaluationContext apiCtx = new ImmutableContext(apiAttrs); api.setEvaluationContext(apiCtx); // set a value to the client context Map clientAttrs = new HashMap<>(); clientAttrs.put("region", new Value(System.getenv("AWS_REGION"))); EvaluationContext clientCtx = new ImmutableContext(clientAttrs); Client client = api.getClient(); client.setEvaluationContext(clientCtx); // set a value to the invocation context Map requestAttrs = new HashMap<>(); requestAttrs.put("email", new Value(session.getAttribute("email"))); requestAttrs.put("product", new Value("productId")); String targetingKey = session.getId(); EvaluationContext reqCtx = new ImmutableContext(targetingKey, requestAttrs); boolean flagValue = client.getBooleanValue("some-flag", false, reqCtx); ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Java) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```java // add a hook globally, to run on all evaluations OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.addHooks(new ExampleHook()); // add a hook on this client, to run on all evaluations made by this client Client client = api.getClient(); client.addHooks(new ExampleHook()); // add a hook for this evaluation only Boolean retval = client.getBooleanValue(flagKey, false, null, FlagEvaluationOptions.builder().hook(new ExampleHook()).build()); ``` ### Tracking The [tracking API](https://openfeature.dev/specification/sections/tracking.md) allows you to use OpenFeature abstractions to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. Note that, unlike methods that handle feature flag evaluations, calling `track(...)` may throw an `IllegalArgumentException` if an empty string is passed as the `trackingEventName`. ```java OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.getClient().track("visited-promo-page", new MutableTrackingEventDetails(99.77).add("currency", "USD")); ``` ### Logging The Java SDK uses SLF4J. See the [SLF4J manual](https://slf4j.org/manual.html) for complete documentation. Note that in accordance with the OpenFeature specification, the SDK doesn't generally log messages during flag evaluation. #### Logging Hook The Java SDK includes a `LoggingHook`, which logs detailed information at key points during flag evaluation, using SLF4J's structured logging API. This hook can be particularly helpful for troubleshooting and debugging; simply attach it at the global, client or invocation level and ensure your log level is set to "debug". See [hooks](#hooks) for more information on configuring hooks. ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the global provider is used. ```java FeatureProvider scopedProvider = new MyProvider(); // registering the default provider OpenFeatureAPI.getInstance().setProvider(new LocalProvider()); // registering a provider to a domain OpenFeatureAPI.getInstance().setProvider("my-domain", new CachedProvider()); // A client bound to the default provider Client clientDefault = OpenFeatureAPI.getInstance().getClient(); // A client bound to the CachedProvider provider Client domainScopedClient = OpenFeatureAPI.getInstance().getClient("my-domain"); ``` Providers for domains can be set in a blocking or non-blocking way. For more details, please refer to the [providers](#providers) section. ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ```java // add an event handler to a client Client client = OpenFeatureAPI.getInstance().getClient(); client.onProviderConfigurationChanged((EventDetails eventDetails) -> { // do something when the provider's flag settings change }); // add an event handler to the global API OpenFeatureAPI.getInstance().onProviderStale((EventDetails eventDetails) -> { // do something when the provider's cache goes stale }); ``` ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```java // shut down all providers OpenFeatureAPI.getInstance().shutdown(); ``` ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). By default, the `NoOpTransactionContextPropagator` is used, which doesn't store anything. To register a `ThreadLocal` context propagator, you can use the `setTransactionContextPropagator` method as shown below. ```java // registering the ThreadLocalTransactionContextPropagator OpenFeatureAPI.getInstance().setTransactionContextPropagator(new ThreadLocalTransactionContextPropagator()); ``` Once you've registered a transaction context propagator, you can propagate the data into request-scoped transaction context. ```java // adding userId to transaction context OpenFeatureAPI api = OpenFeatureAPI.getInstance(); Map transactionAttrs = new HashMap<>(); transactionAttrs.put("userId", new Value("userId")); EvaluationContext transactionCtx = new ImmutableContext(transactionAttrs); api.setTransactionContext(transactionCtx); ``` Additionally, you can develop a custom transaction context propagator by implementing the `TransactionContextPropagator` interface and registering it as shown above. ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/java-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. ```java public class MyProvider implements FeatureProvider { @Override public Metadata getMetadata() { return () -> "My Provider"; } @Override public void initialize(EvaluationContext evaluationContext) throws Exception { // start up your provider } @Override public void shutdown() { // shut down your provider } @Override public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { // resolve a boolean flag value } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { // resolve a string flag value } @Override public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { // resolve an int flag value } @Override public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { // resolve a double flag value } @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { // resolve an object flag value } } ``` If you'd like your provider to support firing events, such as events for when flags are changed in the flag management system, extend `EventProvider`. ```java class MyEventProvider extends EventProvider { @Override public Metadata getMetadata() { return () -> "My Event Provider"; } @Override public void initialize(EvaluationContext evaluationContext) throws Exception { // emit events when flags are changed in a hypothetical REST API this.restApiClient.onFlagsChanged(() -> { ProviderEventDetails details = ProviderEventDetails.builder().message("flags changed in API!").build(); this.emitProviderConfigurationChanged(details); }); } @Override public void shutdown() { // shut down your provider } // remaining provider methods... } ``` Providers no longer need to manage their own state, this is done by the SDK itself. If desired, the state of a provider can be queried through the client that uses the provider. ```java OpenFeatureAPI.getInstance().getClient().getProviderState(); ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/java-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the `Hook interface`. ```java class MyHook implements Hook { @Override public Optional before(HookContext ctx, Map hints) { // code that runs before the flag evaluation return Optional.empty(); } @Override public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) { // code that runs after the flag evaluation succeeds } @Override public void error(HookContext ctx, Exception error, Map hints) { // code that runs when there's an error during a flag evaluation } @Override public void finallyAfter(HookContext ctx, FlagEvaluationDetails details, Map hints) { // code that runs regardless of success or error } }; ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature Node.js SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v1.23.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.23.0)
[![API Reference](https://img.shields.io/badge/reference-teal?logo=javascript\&logoColor=white)](https://open-feature.github.io/js-sdk/modules/_openfeature_server_sdk.html)[![NPM Download](https://img.shields.io/npm/dm/%40openfeature%2Fserver-sdk)](https://www.npmjs.com/package/@openfeature/server-sdk)[![codecov](https://codecov.io/gh/open-feature/js-sdk/branch/main/graph/badge.svg?token=3DC5XOEHMY)](https://codecov.io/gh/open-feature/js-sdk)[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6594/badge)](https://bestpractices.coreinfrastructure.org/projects/6594) ## Quick start ### Requirements * Node.js version 18+ ### Install #### npm ```sh npm install --save @openfeature/server-sdk ``` Tip This SDK is designed to run in Node.JS. If you're interested in browser support, check out the [Web SDK](https://openfeature.dev/docs/reference/sdks/client/web.md). #### yarn ```sh # yarn requires manual installation of the @openfeature/core peer-dependency yarn add @openfeature/server-sdk @openfeature/core ``` Note `@openfeature/core` contains common components used by all OpenFeature JavaScript implementations. Every SDK version has a requirement on a single, specific version of this dependency. For more information, and similar implications on libraries developed with OpenFeature see [considerations when extending](#considerations). ### Usage ```ts import { OpenFeature } from '@openfeature/server-sdk'; // Register your feature flag provider try { await OpenFeature.setProviderAndWait(new YourProviderOfChoice()); } catch (error) { console.error('Failed to initialize provider:', error); } // create a new client const client = OpenFeature.getClient(); // Evaluate your feature flag const v2Enabled = await client.getBooleanValue('v2_enabled', false); if (v2Enabled) { console.log('v2 is enabled'); } ``` ### API Reference See [here](https://open-feature.github.io/js-sdk/modules/_openfeature_server_sdk.html) for the complete API documentation. ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread) | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations, particularly for A/B testing. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | | ✅ | [Multi-Provider](#multi-provider) | Combine multiple providers with configurable evaluation strategies. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Server-side\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=JavaScript) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: #### Awaitable To register a provider and ensure it is ready before further actions are taken, you can use the `setProviderAndWait` method as shown below: ```ts await OpenFeature.setProviderAndWait(new MyProvider()); ``` #### Synchronous To register a provider in a synchronous manner, you can use the `setProvider` method as shown below: ```ts OpenFeature.setProvider(new MyProvider()); ``` Once the provider has been registered, the status can be tracked using [events](#eventing). In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. #### Multi-Provider The Multi-Provider allows you to use multiple underlying providers as sources of flag data for the OpenFeature server SDK. When a flag is being evaluated, the Multi-Provider will consult each underlying provider it is managing in order to determine the final result. Different evaluation strategies can be defined to control which providers get evaluated and which result is used. The Multi-Provider is a powerful tool for performing migrations between flag providers, or combining multiple providers into a single feature flagging interface. For example: * **Migration**: Gradually migrate from one provider to another by serving some flags from your old provider and some from your new provider * **Backup**: Use one provider as a backup for another in case of failures * **Comparison**: Compare results from multiple providers to validate consistency * **Hybrid**: Combine multiple providers to leverage different strengths (e.g., one for simple flags, another for complex targeting) ```ts import { OpenFeature, MultiProvider, FirstMatchStrategy } from '@openfeature/server-sdk'; // Create providers const primaryProvider = new YourPrimaryProvider(); const backupProvider = new YourBackupProvider(); // Create multi-provider with a strategy const multiProvider = new MultiProvider( [{ provider: primaryProvider }, { provider: backupProvider }], new FirstMatchStrategy(), ); // Register the multi-provider await OpenFeature.setProviderAndWait(multiProvider); // Use as normal const client = OpenFeature.getClient(); const value = await client.getBooleanValue('my-flag', false); ``` **Available Strategies:** * `FirstMatchStrategy`: Returns the first successful result from the list of providers (short-circuits on error) * `FirstSuccessfulStrategy`: Returns the first successful result, ignoring errors from earlier providers * `ComparisonStrategy`: Compares results from multiple providers and can handle discrepancies **Migration Example:** ```ts import { OpenFeature, MultiProvider, FirstMatchStrategy } from '@openfeature/server-sdk'; // During migration, serve some flags from the new provider and fallback to the old one const newProvider = new NewFlagProvider(); const oldProvider = new OldFlagProvider(); const multiProvider = new MultiProvider( [{ provider: newProvider }, { provider: oldProvider }], // New provider is consulted first new FirstMatchStrategy(), ); await OpenFeature.setProviderAndWait(multiProvider); ``` **Comparison Example:** ```ts import { OpenFeature, MultiProvider, ComparisonStrategy } from '@openfeature/server-sdk'; // Compare results from two providers for validation const providerA = new ProviderA(); const providerB = new ProviderB(); const multiProvider = new MultiProvider( [{ provider: providerA }, { provider: providerB }], new ComparisonStrategy(providerA, (resolutions) => { console.warn('Mismatch detected', resolutions); }), ); await OpenFeature.setProviderAndWait(multiProvider); ``` ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```ts // set a value to the global context OpenFeature.setContext({ region: 'us-east-1' }); // set a value to the client context const client = OpenFeature.getClient(); client.setContext({ version: process.env.APP_VERSION }); // set a value to the invocation context const requestContext = { targetingKey: req.session.id, email: req.session.email, product: req.productId, }; const boolValue = await client.getBooleanValue('some-flag', false, requestContext); ``` Context is merged by the SDK before a flag evaluation occurs. The merge order is defined [here](https://openfeature.dev/specification/sections/evaluation-context.md#requirement-323) in the OpenFeature specification. ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Server-side\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=JavaScript) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```ts import { OpenFeature } from '@openfeature/server-sdk'; // add a hook globally, to run on all evaluations OpenFeature.addHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client const client = OpenFeature.getClient(); client.addHooks(new ExampleClientHook()); // add a hook for this evaluation only const boolValue = await client.getBooleanValue('bool-flag', false, { hooks: [new ExampleHook()] }); ``` ### Logging The Node.JS SDK will log warnings and errors to the console by default. This behavior can be overridden by passing a custom logger either globally or per client. A custom logger must implement the [Logger interface](https://github.com/open-feature/js-sdk/blob/main/packages/shared/src/logger/logger.ts). ```ts import type { Logger } from '@openfeature/server-sdk'; // The logger can be anything that conforms with the Logger interface const logger: Logger = console; // Sets a global logger OpenFeature.setLogger(logger); // Sets a client logger const client = OpenFeature.getClient(); client.setLogger(logger); ``` ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```ts import { OpenFeature, TypedInMemoryProvider } from '@openfeature/server-sdk'; const myFlags = { v2_enabled: { variants: { on: true, off: false, }, disabled: false, defaultVariant: 'on', }, } as const; // Registering the default provider OpenFeature.setProvider(new TypedInMemoryProvider(myFlags)); // Registering a provider to a domain OpenFeature.setProvider('my-domain', new TypedInMemoryProvider(someOtherFlags)); // A Client bound to the default provider const clientWithDefault = OpenFeature.getClient(); // A Client bound to the TypedInMemoryProvider provider const domainScopedClient = OpenFeature.getClient('my-domain'); ``` Domains can be defined on a provider during registration. For more details, please refer to the [providers](#providers) section. ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ```ts import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; // OpenFeature API OpenFeature.addHandler(ProviderEvents.Ready, (eventDetails) => { console.log(`Ready event from: ${eventDetails?.providerName}:`, eventDetails); }); // Specific client const client = OpenFeature.getClient(); client.addHandler(ProviderEvents.Error, (eventDetails) => { console.log(`Error event from: ${eventDetails?.providerName}:`, eventDetails); }); ``` ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). The following example shows an Express middleware using transaction context propagation to propagate the request ip and user id into request scoped transaction context. ```ts import express, { Request, Response, NextFunction } from 'express'; import { OpenFeature, AsyncLocalStorageTransactionContextPropagator } from '@openfeature/server-sdk'; OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContextPropagator()); /** * This example is based on an express middleware. */ const app = express(); app.use((req: Request, res: Response, next: NextFunction) => { const ip = req.headers['x-forwarded-for']; OpenFeature.setTransactionContext({ targetingKey: req.user.id, ipAddress: ip }, () => { // The transaction context is used in any flag evaluation throughout the whole call chain of next next(); }); }); ``` ### Tracking The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. ```ts // flag is evaluated await client.getBooleanValue('new-feature', false); // new feature is used and track function is called recording the usage useNewFeature(); client.track('new-feature-used'); ``` ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```ts import { OpenFeature } from '@openfeature/server-sdk'; await OpenFeature.close(); ``` ### Type-Safe Flag Keys For enhanced type safety and autocompletion, you can override flag key types using TypeScript module augmentation. See the [`@openfeature/core` README](https://github.com/open-feature/js-sdk/blob/main/packages/shared/README.md#type-safe-flag-keys) for details. ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/js-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the [Provider interface](https://github.com/open-feature/js-sdk/blob/main/packages/server/src/provider/provider.ts) exported by the OpenFeature SDK. ```ts import { AnyProviderEvent, EvaluationContext, Hook, JsonValue, Logger, Provider, ProviderEventEmitter, ResolutionDetails, } from '@openfeature/server-sdk'; // implement the provider interface class MyProvider implements Provider { // Adds runtime validation that the provider is used with the expected SDK public readonly runsOn = 'server'; readonly metadata = { name: 'My Provider', } as const; // Optional provider managed hooks hooks?: Hook[]; resolveBooleanEvaluation( flagKey: string, defaultValue: boolean, context: EvaluationContext, logger: Logger, ): Promise> { // code to evaluate a boolean } resolveStringEvaluation( flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger, ): Promise> { // code to evaluate a string } resolveNumberEvaluation( flagKey: string, defaultValue: number, context: EvaluationContext, logger: Logger, ): Promise> { // code to evaluate a number } resolveObjectEvaluation( flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger, ): Promise> { // code to evaluate an object } // implement with "new OpenFeatureEventEmitter()", and use "emit()" to emit events events?: ProviderEventEmitter | undefined; initialize?(context?: EvaluationContext | undefined): Promise { // code to initialize your provider } onClose?(): Promise { // code to shut down your provider } } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/js-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the [Hook interface](https://github.com/open-feature/js-sdk/blob/main/packages/shared/src/hooks/hook.ts). ```ts import type { Hook, HookContext, EvaluationDetails, FlagValue } from '@openfeature/server-sdk'; export class MyHook implements Hook { after(hookContext: HookContext, evaluationDetails: EvaluationDetails) { // code that runs after flag values are successfully resolved from the provider } } ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! ### Considerations When developing a library based on OpenFeature components, it's important to list the `@openfeature/server-sdk` as a `peerDependency` of your package. This is a general best-practice when developing JavaScript libraries that have dependencies in common with their consuming application. Failing to do this can result in multiple copies of the OpenFeature SDK in the consumer, which can lead to type errors, and broken singleton behavior. The `@openfeature/core` package itself follows this pattern: the `@openfeature/server-sdk` has a peer dependency on `@openfeature/core`, and uses whatever copy of that module the consumer has installed (note that NPM installs peers automatically unless `--legacy-peer-deps` is set, while yarn does not, and PNPM does so based on its configuration). When developing such libraries, it's NOT necessary to add a `peerDependency` on `@openfeature/core`, since the `@openfeature/server-sdk` establishes that dependency itself transitively. --- # OpenFeature NestJS SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.2.7\&color=blue\&style=for-the-badge)](https://github.com/open-feature/js-sdk/releases/tag/nestjs-sdk-v0.2.7)
[![codecov](https://codecov.io/gh/open-feature/js-sdk/branch/main/graph/badge.svg?token=3DC5XOEHMY)](https://codecov.io/gh/open-feature/js-sdk)[![NPM Download](https://img.shields.io/npm/dm/%40openfeature%2Fnestjs-sdk)](https://www.npmjs.com/package/@openfeature/nestjs-sdk) ## Overview The OpenFeature NestJS SDK is a package that provides a NestJS wrapper for the [OpenFeature Server SDK](https://openfeature.dev/docs/reference/sdks/server/javascript.md). Capabilities include: * Providing a NestJS global module to simplify OpenFeature configuration and usage within NestJS * Setting up logging, event handling, hooks and providers directly when registering the module * Injecting feature flags directly into controller route handlers by using decorators * Injecting transaction evaluation context for flag evaluations directly from [execution context](https://docs.nestjs.com/fundamentals/execution-context) (HTTP header values, client IPs, etc.) * Injecting OpenFeature clients into NestJS services and controllers by using decorators ## Quick start ### Requirements * Node.js version 20+ * NestJS version 8+ ### Install #### npm ```sh npm install --save @openfeature/nestjs-sdk ``` #### yarn ```sh # yarn requires manual installation of the peer dependencies (see below) yarn add @openfeature/nestjs-sdk @openfeature/server-sdk @openfeature/core ``` #### Required peer dependencies The following list contains the peer dependencies of `@openfeature/nestjs-sdk` with its expected and compatible versions: * `@openfeature/server-sdk`: ^1.17.1 * `@nestjs/common`: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 * `@nestjs/core`: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 * `rxjs`: ^7.0.0 || ^8.0.0 The minimum required version of `@openfeature/server-sdk` currently is `1.17.1`. ### Usage The example below shows how to use the `OpenFeatureModule` with OpenFeature's `TypedInMemoryProvider`. ```ts import { Module } from '@nestjs/common'; import { OpenFeatureModule, TypedInMemoryProvider } from '@openfeature/nestjs-sdk'; @Module({ imports: [ OpenFeatureModule.forRoot({ defaultProvider: new TypedInMemoryProvider({ testBooleanFlag: { defaultVariant: 'default', variants: { default: true }, disabled: false, }, }), providers: { differentProvider: new TypedInMemoryProvider(), }, }), ], }) export class AppModule {} ``` With the `OpenFeatureModule` configured, it's possible to inject flag evaluation details into route handlers like in the following code snippet. ```ts import { Controller, ExecutionContext, Get } from '@nestjs/common'; import { map, Observable } from 'rxjs'; import { BooleanFeatureFlag, EvaluationDetails } from '@openfeature/nestjs-sdk'; import { Request } from 'express'; @Controller() export class OpenFeatureController { @Get('/welcome') public async welcome( @BooleanFeatureFlag({ flagKey: 'testBooleanFlag', defaultValue: false, }) feature: Observable>, ) { return feature.pipe( map((details) => details.value ? 'Welcome to this OpenFeature-enabled NestJS app!' : 'Welcome to this NestJS app!', ), ); } } ``` It is also possible to inject the default or domain scoped OpenFeature clients into a service via Nest dependency injection system. ```ts import { Injectable } from '@nestjs/common'; import { OpenFeatureClient, Client } from '@openfeature/nestjs-sdk'; @Injectable() export class OpenFeatureTestService { constructor( @OpenFeatureClient() private defaultClient: Client, @OpenFeatureClient({ domain: 'my-domain' }) private scopedClient: Client, ) {} public async getBoolean() { return await this.defaultClient.getBooleanValue('testBooleanFlag', false); } } ``` #### Managing Controller or Route Access via Feature Flags The `RequireFlagsEnabled` decorator can be used to manage access to a controller or route based on the enabled state of a feature flag. The decorator will throw an exception if the required feature flag(s) are not enabled. ```ts import { Controller, Get } from '@nestjs/common'; import { RequireFlagsEnabled } from '@openfeature/nestjs-sdk'; @Controller() export class OpenFeatureController { @RequireFlagsEnabled({ flags: [{ flagKey: 'testBooleanFlag' }] }) @Get('/welcome') public async welcome() { return 'Welcome to this OpenFeature-enabled NestJS app!'; } } ``` #### Type-Safe Flag Keys For enhanced type safety and autocompletion, you can override flag key types using TypeScript module augmentation. See the [`@openfeature/core` README](https://github.com/open-feature/js-sdk/blob/main/packages/shared/README.md#type-safe-flag-keys) for details. ## Module additional information ### Flag evaluation context injection Whenever a flag evaluation occurs, context can be provided with information like user e-mail, role, targeting key, etc. in order to trigger specific evaluation rules or logic. The `OpenFeatureModule` provides a way to configure context for each request using the `contextFactory` option. The `contextFactory` is run in a NestJS interceptor scope to configure the evaluation context, and then it is used in every flag evaluation related to this request. By default, the interceptor is configured globally, but it can be changed by setting the `useGlobalInterceptor` to `false`. In this case, it is still possible to configure a `contextFactory` that can be injected into route, module or controller bound interceptors. --- # OpenFeature PHP SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.5.1\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.5.1)[![Release](https://img.shields.io/static/v1?label=release\&message=v2.3.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/php-sdk/releases/tag/2.3.0)
[![Total Downloads](https://poser.pugx.org/open-feature/sdk/downloads)](https://packagist.org/packages/open-feature/sdk)[![PHP 8.0+](https://img.shields.io/badge/php->=8.0-blue.svg)]()[![License](https://poser.pugx.org/open-feature/sdk/license)](https://packagist.org/packages/open-feature/sdk)[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/6853/badge)](https://bestpractices.coreinfrastructure.org/projects/6853) ## Quick start ### Requirements This library targets PHP version 8.0 and newer. As long as you have any compatible version of PHP on your system you should be able to utilize the OpenFeature SDK. This package also has a `.tool-versions` file for use with PHP version managers like `asdf`. ### Install ```shell composer require open-feature/sdk ``` ### Usage ```php use OpenFeature\OpenFeatureAPI; use OpenFeature\Providers\Flagd\FlagdProvider; function example() { $api = OpenFeatureAPI::getInstance(); // configure a provider $api->setProvider(new FlagdProvider()); // create a client $client = $api->getClient(); // get a bool flag value $client->getBooleanValue('v2_enabled', false); } ``` #### Extended Example ```php use OpenFeature\OpenFeatureAPI; use OpenFeature\OpenFeatureClient; class MyClass { private OpenFeatureClient $client; public function __construct() { $this->client = OpenFeatureAPI::getInstance()->getClient('MyClass'); } public function booleanExample(): UI { // Should we render the redesign? Or the default webpage? if ($this->client->getBooleanValue('redesign_enabled', false)) { return render_redesign(); } return render_normal(); } public function stringExample(): Template { // Get the template to load for the custom new homepage $template = $this->client->getStringValue('homepage_template', 'default-homepage.html'); return render_template($template); } public function numberExample(): array { // How many modules should we be fetching? $count = $this->client->getIntegerValue('module-fetch-count', 4); return fetch_modules($count); } public function structureExample(): HomepageModule { $obj = $this->client->getObjectValue('hero-module', $previouslyDefinedDefaultStructure); return HomepageModuleBuilder::new() ->title($obj->getValue('title')) ->body($obj->getValue('description')) ->build(); } } ``` ## Features | Status | Features | Description | | ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [MultiProvider](#multiprovider) | Combine multiple providers with configurable evaluation strategies for fallback and aggregation. | | ❌ | [Named clients](#named-clients) | Utilize multiple providers in a single application. | | ⚠️ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ❌ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=PHP) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```php $api = OpenFeatureAPI::getInstance(); $api->setProvider(new MyProvider()); ``` #### MultiProvider The Multi-Provider allows you to use multiple underlying providers as sources of flag data for the OpenFeature SDK. When a flag is being evaluated, the Multi-Provider will consult each underlying provider it is managing in order to determine the final result. Different evaluation strategies can be defined to control which providers get evaluated and which result is used. The Multi-Provider is a powerful tool for performing migrations between flag providers, or combining multiple providers into a single feature flagging interface. For example: * **Migration**: When migrating between two providers, you can run both in parallel under a unified flagging interface. As flags are added to the new provider, the Multi-Provider will automatically find and return them, falling back to the old provider if the new provider does not have the flag. * **Multiple Data Sources**: The Multi-Provider allows you to seamlessly combine many sources of flagging data, such as environment variables, local files, database values, and SaaS-hosted feature management systems. * **High Availability**: Use multiple providers as redundant sources, automatically failing over when one provider is unavailable. **Basic Usage** The Multi-Provider is initialized with an array of providers it should evaluate: ```php use OpenFeature\OpenFeatureAPI; use OpenFeature\implementation\multiprovider\MultiProvider; $multiProvider = new MultiProvider([ ['provider' => new ProviderA()], ['provider' => new ProviderB()], ]); $api = OpenFeatureAPI::getInstance(); $api->setProvider($multiProvider); $client = $api->getClient(); echo $client->getBooleanValue('my-flag', false); ``` By default, the Multi-Provider will evaluate all underlying providers in order and return the **first successful result**. If a provider indicates it does not have a flag (`FLAG_NOT_FOUND` error code), then it will be skipped and the next provider will be evaluated. If any provider throws or returns an error result, the operation will fail and the error will be returned. If no provider returns a successful result, the operation will fail with a `FLAG_NOT_FOUND` error code. To change this behaviour, a different "strategy" can be provided: ```php use OpenFeature\implementation\multiprovider\strategy\FirstSuccessfulStrategy; $multiProvider = new MultiProvider([ ['provider' => new ProviderA()], ['provider' => new ProviderB()], ], new FirstSuccessfulStrategy()); ``` The Multi-Provider comes with three strategies out of the box: 1. **FirstMatchStrategy** (default): Evaluates all providers in order and returns the first successful result. Providers that indicate `FLAG_NOT_FOUND` error will be skipped and the next provider will be evaluated. Any other error will cause the operation to fail and the error to be returned. 2. **FirstSuccessfulStrategy**: Evaluates all providers in order and returns the first successful result. Any error will cause that provider to be skipped. If no successful result is returned, the set of errors will be returned. 3. **ComparisonStrategy**: Evaluates all providers at one time. If every provider returns a successful result with the same value, then that result is returned. Otherwise, an error is returned immediately if any provider errors. When values do not agree, an optional callback will be executed to notify you of the mismatch, and the configured "fallback provider" value will be used. This can be useful when migrating between providers that are expected to contain identical configuration. You can easily spot mismatches in configuration without affecting flag behaviour. **Provider Naming** Providers can be named explicitly or have names auto-generated: ```php // Explicit naming (recommended for clarity) new MultiProvider([ ['name' => 'LaunchDarkly', 'provider' => $ldProvider], ['name' => 'FlagSmith', 'provider' => $fsProvider], ]); // Auto-generated naming (provider class name is used) new MultiProvider([ ['provider' => $ldProvider], // Named "launchdarkly" (lowercase) ['provider' => $fsProvider], // Named "flagsmith" (lowercase) ['provider' => $fsProvider], // Named "flagsmith_2" (auto-incremented, lowercase) ]); ``` > **Note:** Provider names are **case-insensitive** and stored in lowercase. "MyProvider", "myprovider", and "MYPROVIDER" are all treated as the same provider name. Auto-generated names from provider metadata are also normalized to lowercase. **Strategies** ##### FirstMatchStrategy (Default) Evaluates providers sequentially and returns the **first successful result**. Continues to the next provider only if the current one throws a `FLAG_NOT_FOUND` error. **Use cases:** * Primary/fallback provider setup * Gradual migration between providers * Provider priority ordering ```php use OpenFeature\implementation\multiprovider\strategy\FirstMatchStrategy; $multiProvider = new MultiProvider([ ['name' => 'Remote', 'provider' => new RemoteProvider()], // Try remote first ['name' => 'Cache', 'provider' => new CacheProvider()], // Fall back to cache ['name' => 'Default', 'provider' => new DefaultProvider()], // Final fallback ], new FirstMatchStrategy()); ``` **Behavior:** * **Remote** has the flag → returns Remote's value ✅ * **Remote** throws `FLAG_NOT_FOUND` → continues to **Cache** * **Remote** throws other error (e.g., network timeout) → stops and returns error ❌ * All providers throw `FLAG_NOT_FOUND` → returns default value with aggregated errors ##### FirstSuccessfulStrategy Evaluates providers sequentially and returns the **first successful result**, skipping providers that throw **any error** (not just `FLAG_NOT_FOUND`). **Use cases:** * High availability setups * Tolerating provider failures * Failover scenarios ```php use OpenFeature\implementation\multiprovider\strategy\FirstSuccessfulStrategy; $multiProvider = new MultiProvider([ ['name' => 'Primary', 'provider' => new UnstableProvider()], ['name' => 'Secondary', 'provider' => new StableProvider()], ['name' => 'Tertiary', 'provider' => new LocalProvider()], ], new FirstSuccessfulStrategy()); ``` **Behavior:** * Evaluates providers in order until one succeeds * Ignores **all types of errors** from failing providers * Returns the first successful result * If all providers fail → returns default value with aggregated errors ##### ComparisonStrategy Evaluates **all providers** at one time. If every provider returns a successful result with the same value, then that result is returned. Otherwise, an error is returned immediately if any provider errors. When values do not agree, the configured "fallback provider" value will be used. This strategy accepts several arguments during initialization: ```php use OpenFeature\implementation\multiprovider\strategy\ComparisonStrategy; // Set up providers $providerA = new ProviderA(); $multiProvider = new MultiProvider([ ['provider' => $providerA], ['provider' => new ProviderB()], ], new ComparisonStrategy( $providerA, // First argument: "fallback provider" whose value to use when providers don't agree function($resolutions) { // Second argument: callback when values don't match error_log('Mismatch detected: ' . json_encode($resolutions)); } )); ``` The first argument is the "fallback provider" whose value to use in the event that providers do not agree. It should be the same object reference as one of the providers in the list. The second argument is a callback function that will be executed when a mismatch is detected. The callback will be passed an array containing the details of each provider's resolution, including the flag key, the value returned, and any errors that were thrown. **Use cases:** * A/B testing during provider migrations * Validating new provider implementations against trusted baselines * Detecting configuration drift across multiple sources * Ensuring provider consistency before committing to a new provider **Behavior:** * Evaluates **ALL providers** sequentially * **Fail-fast on errors:** If ANY provider returns an error, immediately returns all errors (no partial results) * **Compares values:** Uses strict equality (`===`) to check if all providers returned the same value * **On agreement:** If all providers succeed and all values match, returns the common value * **On mismatch:** If all succeed but values don't match, calls optional `onMismatch` callback, then returns the fallback provider's value * **Throws exception** if fallback provider not found in results **Important Notes:** * The fallback provider parameter is **required** (not optional) * Fallback provider must be included in the provider list * This is **not** a "highest value wins" strategy - it checks for exact equality * Fallback provider is only used for value mismatches, NOT for error recovery * Useful for ensuring consistency during migrations, not for aggregating different values **Strategy Comparison** | Scenario | FirstMatchStrategy | FirstSuccessfulStrategy | ComparisonStrategy | | ------------------------------- | ----------------------------- | ------------------------------- | ------------------------------ | | Provider order matters | ✅ Yes (stops at first match) | ✅ Yes (stops at first success) | ❌ No (evaluates all) | | Handles FLAG_NOT_FOUND | Continues to next | Continues to next | Returns all errors | | Handles other errors | Stops evaluation | Continues to next | Returns all errors immediately | | Evaluates all providers | ❌ No | ❌ No | ✅ Yes | | Best for fallback | ✅ | ✅ | ❌ | | Best for high availability | ❌ | ✅ | ❌ | | Best for consistency validation | ❌ | ❌ | ✅ | | Requires fallback provider | ❌ Optional | ❌ Optional | ✅ Required | **Error Handling** Error handling varies by strategy: **FirstMatchStrategy:** * Continues evaluation when providers throw `FLAG_NOT_FOUND` * Stops and returns error if provider throws other errors * Aggregates `FLAG_NOT_FOUND` errors if all providers fail **FirstSuccessfulStrategy:** * Continues evaluation when providers throw any error * Returns first successful result, ignoring all previous errors * Aggregates all errors if all providers fail **ComparisonStrategy:** * **Fail-fast:** Returns all errors immediately if ANY provider errors * Fallback provider only used for value mismatches (not error recovery) * All providers are evaluated (no short-circuiting on mismatches) * On value mismatch: Invokes `onMismatch` callback, then returns fallback provider's value (not an error) **Error Aggregation:** When all providers fail, MultiProvider aggregates individual provider errors into a single detailed error message: ```php // Example: FirstMatchStrategy with all FLAG_NOT_FOUND $result = $client->getBooleanDetails('non-existent-flag', false); // Returns: value=false, reason=ERROR, // error="Multi-provider evaluation failed with 3 provider error(s): [ProviderA]: Flag not found; [ProviderB]: Flag not found; [ProviderC]: Flag not found" // Example: ComparisonStrategy with provider errors $result = $client->getBooleanDetails('some-flag', false); // Returns: value=false, reason=ERROR, // error="Multi-provider evaluation failed with 2 provider error(s): [ProviderA]: Connection timeout; [ProviderB]: Invalid configuration" ``` This detailed error aggregation helps with debugging by showing exactly which provider failed and why, similar to JavaScript's `AggregateError`. **Complete Example** ```php use OpenFeature\OpenFeatureAPI; use OpenFeature\implementation\multiprovider\MultiProvider; use OpenFeature\implementation\multiprovider\strategy\FirstMatchStrategy; // Set up providers $remoteProvider = new LaunchDarklyProvider($config); $cacheProvider = new RedisCacheProvider($redis); $defaultProvider = new InMemoryProvider([ 'feature-enabled' => true, 'api-timeout' => 30, ]); // Configure MultiProvider with priority order $multiProvider = new MultiProvider([ ['name' => 'LaunchDarkly', 'provider' => $remoteProvider], ['name' => 'Redis', 'provider' => $cacheProvider], ['name' => 'Defaults', 'provider' => $defaultProvider], ], new FirstMatchStrategy()); // Register with OpenFeature $api = OpenFeatureAPI::getInstance(); $api->setProvider($multiProvider); // Use as normal $client = $api->getClient(); $enabled = $client->getBooleanValue('feature-enabled', false); // Evaluation flow: // 1. Try LaunchDarkly → if FLAG_NOT_FOUND, continue // 2. Try Redis → if FLAG_NOT_FOUND, continue // 3. Try Defaults → returns value // 4. If all fail → returns default with aggregated errors ``` **Custom Strategies** It is also possible to implement your own strategy if the above options do not fit your use case. To do so, create a class which extends `BaseEvaluationStrategy`: ```php use OpenFeature\implementation\multiprovider\strategy\BaseEvaluationStrategy; use OpenFeature\implementation\multiprovider\strategy\ProviderContext; use OpenFeature\implementation\multiprovider\strategy\StrategyContext; use OpenFeature\implementation\multiprovider\ProviderResolutionResult; use OpenFeature\implementation\multiprovider\FinalResult; use OpenFeature\interfaces\provider\RunMode; class CustomStrategy extends BaseEvaluationStrategy { // Set to RunMode::EVALUATE_ALL to evaluate all providers at one time // or leave as default for sequential evaluation public string $runMode = RunMode::SEQUENTIAL; /** * Called before each provider is evaluated. * Return false to skip evaluating this provider. */ public function shouldEvaluateThisProvider(ProviderContext $context): bool { // Custom logic to determine if this provider should be evaluated return true; } /** * Called after a provider is evaluated (sequential mode only). * Return false to stop evaluating remaining providers. */ public function shouldEvaluateNextProvider( ProviderContext $context, ProviderResolutionResult $result ): bool { // Custom logic to determine if evaluation should continue return true; } /** * Called after all providers have been evaluated. * Determines the final result to return based on all provider results. */ public function determineFinalResult( StrategyContext $context, array $resolutions ): FinalResult { // Custom logic to determine which result to return // Can analyze all results and choose one, aggregate them, etc. // Example: Return first successful result foreach ($resolutions as $resolution) { if (!$resolution->hasError()) { return new FinalResult( $resolution->getDetails(), $resolution->getProviderName(), null ); } } // All failed - return errors return new FinalResult(null, null, $this->aggregateErrors($resolutions)); } } ``` The `$runMode` property determines whether the list of providers will be evaluated sequentially or at once (using `RunMode::EVALUATE_ALL`). The `shouldEvaluateThisProvider()` method is called just before a provider is evaluated by the Multi-Provider. If the function returns false, then the provider will be skipped instead of being evaluated. The `shouldEvaluateNextProvider()` method is called after a provider is evaluated in sequential mode. If it returns true, the next provider in the sequence will be called, otherwise no more providers will be evaluated. This method is not called when `$runMode` is `RunMode::EVALUATE_ALL`. The `determineFinalResult()` method is called after all providers have been evaluated, or the `shouldEvaluateNextProvider()` method returned false. It is called with an array of results from all the individual providers' evaluations. It returns the final result, or can throw an error if needed. #### Known Limitations **Sub-Provider Hooks Not Executed:** Currently, when using MultiProvider, hooks registered on individual sub-providers via `provider->getHooks()` are **not executed** during flag evaluation. Only hooks registered at the API, Client, and Invocation levels (plus MultiProvider's own hooks) are executed. In the JS-SDK reference implementation, each sub-provider's hooks are executed around each provider call via a dedicated `HookExecutor`. The PHP `Provider` interface extends `HooksGetter` (per OpenFeature Requirement 2.10), so the mechanism exists—it just isn't wired up in the current implementation. **Workaround:** Register hooks at the API or Client level instead of on individual providers: ```php // Instead of this (won't execute): $providerA->addHooks($myHook); // Do this (will execute): $client = $api->getClient(); $client->addHooks($myHook); ``` This limitation will be addressed in a future release where per-provider hook execution will be implemented to match the JS-SDK behavior. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```php // add a value to the global context $api = OpenFeatureAPI::getInstance(); $api->setEvaluationContext(new EvaluationContext('targetingKey', new Attributes(['myGlobalKey' => 'myGlobalValue']))); // add a value to the client context $client = $api->getClient(); $client->setEvaluationContext(new EvaluationContext('targetingKey', new Attributes(['myClientKey' => 'myClientValue']))); // add a value to the invocation context $context = new EvaluationContext('targetingKey', new Attributes(['myInvocationKey' => 'myInvocationValue'])); $boolValue = $client->getBooleanValue('boolFlag', false, $context); ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=php) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```php // add a hook globally, to run on all evaluations $api = OpenFeatureAPI::getInstance(); $api->addHooks(new ExampleGlobalHook()); // add a hook on this client, to run on all evaluations made by this client $client = $api->getClient(); $client->addHooks(new ExampleClientHook()); // add a hook for this evaluation only $value = $client->getBooleanValue("boolFlag", false, $context, new EvaluationOptions([new ExampleInvocationHook()])); ``` ### Logging The PHP SDK utilizes several of the PHP Standards Recommendation, one of those being [PSR-3](https://www.php-fig.org/psr/psr-3/) which provides a standard `LoggerInterface`. The SDK makes use of a `LoggerAwareTrait` on several components, including the client for flag evaluation, the hook executor, and the global `OpenFeatureAPI` instance. When an OpenFeature client is created by the API, it will automatically utilize the configured logger in the API for it. The logger set in the client is also automatically used for the hook execution. > ⚠️ Once the client is instantiated, updates to the API's logger will not synchronize. This is done to support the separation of named clients. If you must update an existing client's logger, do so directly! ```php $api = OpenFeatureAPI::getInstance(); $logger = new FancyLogger(); $defaultLoggerClient = $api->getClient('default-logger'); $api->setLogger(new CustomLogger()); $customLoggerClient = $api->getClient('custom-logger'); $overrideLoggerClient = $api->getClient('override-logger'); $overrideLoggerClient->setLogger($logger); // now let's do some evaluations with these! $defaultLoggerClient->getBooleanValue('A', false); // uses default logger in the SDK $customLoggerClient->getBooleanValue('B', false); // uses the CustomLogger set in the API before the client was made $overrideLoggerClient->getBooleanValue('C', false); // uses the FancyLogger set directly on the client ``` ### Named clients Named clients are not yet available in the PHP SDK. Progress on this feature can be tracked [here](https://github.com/open-feature/php-sdk/issues/93). ### Eventing Events are not yet available in the PHP SDK. Progress on this feature can be tracked [here](https://github.com/open-feature/php-sdk/issues/93). ### Shutdown A shutdown method is not yet available in the PHP SDK. Progress on this feature can be tracked [here](https://github.com/open-feature/php-sdk/issues/93). ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/php-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `Provider` interface exported by the OpenFeature SDK. ```php declare(strict_types=1); namespace OpenFeature\Example\Providers; use OpenFeature\implementation\common\Metadata; use OpenFeature\interfaces\common\Metadata as IMetadata; use OpenFeature\interfaces\flags\EvaluationContext; use OpenFeature\interfaces\hooks\Hook; use OpenFeature\interfaces\provider\Provider; use OpenFeature\interfaces\provider\ResolutionDetails; class ExampleProviderImplementation implements Provider { public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; // or, utilize the OpenFeature\interfaces\common\LoggerAwareTrait } /** * @return Hook[] */ public function getHooks(): array { return $this->hooks; // implement according to the OpenFeature specification } /** * Returns the metadata for the current resource */ public function getMetadata(): IMetadata { return new Metadata(self::class); } public function resolveBooleanValue(string $flagKey, bool $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // resolve some ResolutionDetails } public function resolveStringValue(string $flagKey, string $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // resolve some ResolutionDetails } public function resolveIntegerValue(string $flagKey, int $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // resolve some ResolutionDetails } public function resolveFloatValue(string $flagKey, float $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // resolve some ResolutionDetails } /** * @inheritdoc */ public function resolveObjectValue(string $flagKey, array $defaultValue, ?EvaluationContext $context = null): ResolutionDetails { // resolve some ResolutionDetails } } ``` As you can see, this ends up requiring some boilerplate to fulfill all of the functionality that a Provider expects. Another option for implementing a provider is to utilize the AbstractProvider base class. This provides some internally wiring and simple scaffolding so you can skip some of it and focus on what's most important: resolving feature flags! ```php declare(strict_types=1); namespace OpenFeature\Example\Providers; use OpenFeature\implementation\provider\AbstractProvider; use OpenFeature\interfaces\flags\EvaluationContext; use OpenFeature\interfaces\provider\ResolutionDetails as ResolutionDetailsInterface; class ExampleProviderExtension extends AbstractProvider { protected static string $NAME = self::class; public function resolveBooleanValue(string $flagKey, bool $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface { // resolve some ResolutionDetails } public function resolveStringValue(string $flagKey, string $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface { // resolve some ResolutionDetails } public function resolveIntegerValue(string $flagKey, int $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface { // resolve some ResolutionDetails } public function resolveFloatValue(string $flagKey, float $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface { // resolve some ResolutionDetails } /** * @inheritdoc */ public function resolveObjectValue(string $flagKey, array $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface { // resolve some ResolutionDetails } } ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/php-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the `Hook` interface. To satisfy the interface, all methods (`before`/`after`/`finally`/`error`) need to be defined. You can also extend one of the typed abstract base classes (`BooleanHook`, `StringHook`, `IntegerHook`, `FloatHook`, `ObjectHook`) which automatically implement `supportsFlagValueType()` for the corresponding flag type. ```php declare(strict_types=1); namespace OpenFeature\Example\Hooks; use OpenFeature\interfaces\flags\EvaluationContext; use OpenFeature\interfaces\flags\FlagValueType; use OpenFeature\interfaces\hooks\Hook; use OpenFeature\interfaces\hooks\HookContext; use OpenFeature\interfaces\hooks\HookHints; use OpenFeature\interfaces\provider\ResolutionDetails; use Throwable; class ExampleStringHookImplementation implements Hook { public function before(HookContext $context, HookHints $hints): ?EvaluationContext { } public function after(HookContext $context, ResolutionDetails $details, HookHints $hints): void { } public function error(HookContext $context, Throwable $error, HookHints $hints): void { } public function finally(HookContext $context, HookHints $hints): void { } public function supportsFlagValueType(string $flagValueType): bool { return $flagValueType === FlagValueType::STRING; } } ``` You can also make use of existing base classes for various types and behaviors. Suppose you want to make this same hook, and have no limitation around extending a base class, you could do the following: ```php declare(strict_types=1); namespace OpenFeature\Example\Hooks; use OpenFeature\implementation\hooks\StringHook; use OpenFeature\interfaces\flags\EvaluationContext; use OpenFeature\interfaces\flags\FlagValueType; use OpenFeature\interfaces\hooks\HookContext; use OpenFeature\interfaces\hooks\HookHints; use OpenFeature\interfaces\provider\ResolutionDetails; use Throwable; class ExampleStringHookExtension extends StringHook { public function before(HookContext $context, HookHints $hints): ?EvaluationContext { } public function after(HookContext $context, ResolutionDetails $details, HookHints $hints): void { } public function error(HookContext $context, Throwable $error, HookHints $hints): void { } public function finally(HookContext $context, HookHints $hints): void { } } ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature Python SDK [![Specification](https://img.shields.io/static/v1?label=Specification\&message=v0.8.0\&color=red\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Latest version](https://img.shields.io/static/v1?label=release\&message=v0.10.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/python-sdk/releases/tag/v0.10.0)
[![Build status](https://github.com/open-feature/python-sdk/actions/workflows/build.yml/badge.svg)](https://github.com/open-feature/python-sdk/actions/workflows/merge.yml)[![Codecov](https://codecov.io/gh/open-feature/python-sdk/branch/main/graph/badge.svg?token=FQ1I444HB3)](https://codecov.io/gh/open-feature/python-sdk)[![Min python version](https://img.shields.io/badge/python->=3.10-blue.svg)](https://www.python.org/downloads/)[![Repo status](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) ## Quick start ### Requirements * Python 3.10+ ### Install #### Pip install ```bash pip install openfeature-sdk==0.10.0 ``` #### requirements.txt ```bash openfeature-sdk==0.10.0 ``` ```python pip install -r requirements.txt ``` ### Usage ```python from openfeature import api from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider # flags defined in memory my_flags = { "v2_enabled": InMemoryFlag("on", {"on": True, "off": False}), } # configure a provider api.set_provider(InMemoryProvider(my_flags)) # create a client client = api.get_client() # get a bool flag value flag_value = client.get_boolean_value("v2_enabled", False) print("Value: " + str(flag_value)) ``` ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread) | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Python) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```python from openfeature import api from openfeature.provider.no_op_provider import NoOpProvider api.set_provider(NoOpProvider()) open_feature_client = api.get_client() ``` `set_provider()` is non-blocking: it registers the provider immediately and runs initialization in a background thread. Flag evaluations during the initialization window return the default value with a `PROVIDER_NOT_READY` error code. Use `set_provider_and_wait()` if you need to ensure the provider is ready before proceeding: ```python # blocks until the provider is initialized (or raises on failure) api.set_provider_and_wait(NoOpProvider()) ``` In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```python from openfeature.api import ( get_client, set_provider, get_evaluation_context, set_evaluation_context, ) from openfeature.evaluation_context import EvaluationContext global_context = EvaluationContext( targeting_key="targeting_key1", attributes={"application": "value1"} ) request_context = EvaluationContext( targeting_key="targeting_key2", attributes={"email": request.form["email"]} ) ## set global context set_evaluation_context(global_context) # merge second context client = get_client(domain="No-op Provider") client.get_string_value("email", "fallback", request_context) ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Python) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```python from openfeature import api from openfeature.api import add_hooks from openfeature.flag_evaluation import FlagEvaluationOptions # set global hooks at the API-level add_hooks([MyHook()]) # or configure them in the client client = api.get_client() client.add_hooks([MyHook()]) # or at the invocation-level options = FlagEvaluationOptions(hooks=[MyHook()]) client.get_boolean_value("my-flag", False, flag_evaluation_options=options) ``` ### Tracking The [tracking API](https://openfeature.dev/specification/sections/tracking.md) allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. ```python from openfeature.track import TrackingEventDetails # initialize a client client = api.get_client() # trigger tracking event action client.track( "visited-promo-page", evaluation_context=EvaluationContext(), tracking_event_details=TrackingEventDetails(99.77).add("currencyCode", "USD"), ) ``` Note that some providers may not support tracking; check the documentation for your provider for more information. ### Logging The OpenFeature SDK logs to the `openfeature` logger using the `logging` package from the Python Standard Library. #### Logging Hook The Python SDK includes a `LoggingHook`, which logs detailed information at key points during flag evaluation, using the `logging` package. This hook can be particularly helpful for troubleshooting and debugging; simply attach it at the global, client or invocation level and ensure your log level is set to "debug". ```python from openfeature import api from openfeature.hook.logging_hook import LoggingHook api.add_hooks([LoggingHook()]) ``` ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the global provider is used. ```python from openfeature import api # Registering the default provider api.set_provider(MyProvider()) # Registering a provider to a domain api.set_provider(MyProvider(), "my-domain") # A client bound to the default provider default_client = api.get_client() # A client bound to the MyProvider provider domain_scoped_client = api.get_client("my-domain") ``` Domains can be defined on a provider during registration. For more details, please refer to the [providers](#providers) section. ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (PROVIDER_READY on success, PROVIDER_ERROR on failure) are dispatched for every provider. Some providers support additional events, such as PROVIDER_CONFIGURATION_CHANGED. Please refer to the documentation of the provider you're using to see what events are supported. ```python from openfeature import api from openfeature.event import EventDetails, ProviderEvent def on_provider_ready(event_details: EventDetails): print(f"Provider {event_details.provider_name} is ready") api.add_handler(ProviderEvent.PROVIDER_READY, on_provider_ready) client = api.get_client() def on_provider_ready(event_details: EventDetails): print(f"Provider {event_details.provider_name} is ready") client.add_handler(ProviderEvent.PROVIDER_READY, on_provider_ready) ``` ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). You can implement a different transaction context propagator by implementing the `TransactionContextPropagator` class exported by the OpenFeature SDK. In most cases you can use `ContextVarsTransactionContextPropagator` as it works for `threads` and `asyncio` using [Context Variables](https://peps.python.org/pep-0567/). The following example shows a **multithreaded** Flask application using transaction context propagation to propagate the request ip and user id into request scoped transaction context. ```python from flask import Flask, request from openfeature import api from openfeature.evaluation_context import EvaluationContext from openfeature.transaction_context import ContextVarsTransactionContextPropagator # Initialize the Flask app app = Flask(__name__) # Set the transaction context propagator api.set_transaction_context_propagator(ContextVarsTransactionContextPropagator()) # Middleware to set the transaction context # You can call api.set_transaction_context anywhere you have information, # you want to have available in the code-paths below the current one. @app.before_request def set_request_transaction_context(): ip = request.headers.get("X-Forwarded-For", request.remote_addr) user_id = request.headers.get( "User-Id" ) # Assuming we're getting the user ID from a header evaluation_context = EvaluationContext( targeting_key=user_id, attributes={"ipAddress": ip} ) api.set_transaction_context(evaluation_context) def create_response() -> str: # This method can be anywhere in our code. # The feature flag evaluation will automatically contain the transaction context merged with other context new_response = api.get_client().get_string_value("response-message", "Hello User!") return f"Message from server: {new_response}" # Example route where we use the transaction context @app.route("/greeting") def some_endpoint(): return create_response() ``` This also works for asyncio based implementations e.g. FastApi as seen in the following example: ```python from fastapi import FastAPI, Request from openfeature import api from openfeature.evaluation_context import EvaluationContext from openfeature.transaction_context import ContextVarsTransactionContextPropagator # Initialize the FastAPI app app = FastAPI() # Set the transaction context propagator api.set_transaction_context_propagator(ContextVarsTransactionContextPropagator()) # Middleware to set the transaction context @app.middleware("http") async def set_request_transaction_context(request: Request, call_next): ip = request.headers.get("X-Forwarded-For", request.client.host) user_id = request.headers.get( "User-Id" ) # Assuming we're getting the user ID from a header evaluation_context = EvaluationContext( targeting_key=user_id, attributes={"ipAddress": ip} ) api.set_transaction_context(evaluation_context) response = await call_next(request) return response def create_response() -> str: # This method can be located anywhere in our code. # The feature flag evaluation will automatically include the transaction context merged with other context. new_response = api.get_client().get_string_value("response-message", "Hello User!") return f"Message from server: {new_response}" # Example route where we use the transaction context @app.get("/greeting") async def some_endpoint(): return create_response() ``` ### Asynchronous Feature Retrieval The OpenFeature API supports asynchronous calls, enabling non-blocking feature evaluations for improved performance, especially useful in concurrent or latency-sensitive scenarios. If a provider *hasn't* implemented asynchronous calls, the client can still be used asynchronously, but calls will be blocking (synchronous). ```python import asyncio from openfeature import api from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider my_flags = {"v2_enabled": InMemoryFlag("on", {"on": True, "off": False})} api.set_provider(InMemoryProvider(my_flags)) client = api.get_client() flag_value = await client.get_boolean_value_async( "v2_enabled", False ) # API calls are suffixed by _async print("Value: " + str(flag_value)) ``` See the [develop a provider](#develop-a-provider) for how to support asynchronous functionality in providers. ### Shutdown The OpenFeature API provides a shutdown function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```python from openfeature import api api.shutdown() ``` ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/python-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `AbstractProvider` class exported by the OpenFeature SDK. ```python from typing import List, Optional, Union from openfeature.evaluation_context import EvaluationContext from openfeature.flag_evaluation import FlagResolutionDetails from openfeature.hook import Hook from openfeature.provider import AbstractProvider, Metadata class MyProvider(AbstractProvider): def get_metadata(self) -> Metadata: ... def get_provider_hooks(self) -> List[Hook]: return [] def resolve_boolean_details( self, flag_key: str, default_value: bool, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[bool]: ... def resolve_string_details( self, flag_key: str, default_value: str, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[str]: ... def resolve_integer_details( self, flag_key: str, default_value: int, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[int]: ... def resolve_float_details( self, flag_key: str, default_value: float, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[float]: ... def resolve_object_details( self, flag_key: str, default_value: Union[dict, list], evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[Union[dict, list]]: ... ``` Providers can also be extended to support async functionality. To support add asynchronous calls to a provider: * Implement the `AbstractProvider` as shown above. * Define asynchronous calls for each data type. ```python class MyProvider(AbstractProvider): ... async def resolve_boolean_details_async( self, flag_key: str, default_value: bool, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[bool]: ... async def resolve_string_details_async( self, flag_key: str, default_value: str, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[str]: ... async def resolve_integer_details_async( self, flag_key: str, default_value: int, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[int]: ... async def resolve_float_details_async( self, flag_key: str, default_value: float, evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[float]: ... async def resolve_object_details_async( self, flag_key: str, default_value: Union[dict, list], evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[Union[dict, list]]: ... ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/python-sdk-contrib) available under the OpenFeature organization. Implement your own hook by creating a hook that inherits from the `Hook` class. Any of the evaluation life-cycle stages (`before`/`after`/`error`/`finally_after`) can be override to add the desired business logic. ```python from openfeature.hook import Hook, HookContext, HookHints from openfeature.flag_evaluation import FlagEvaluationDetails, FlagValueType class MyHook(Hook): def after( self, hook_context: HookContext, details: FlagEvaluationDetails[FlagValueType], hints: HookHints, ): print("This runs after the flag has been evaluated") ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature Ruby SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.8.0\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.8.0)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.6.5\&color=blue\&style=for-the-badge)](https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.5)[![Build](https://img.shields.io/github/actions/workflow/status/open-feature/ruby-sdk/main.yml?style=for-the-badge)](https://github.com/open-feature/ruby-sdk/actions/workflows/main.yml)[![Gem Version](https://img.shields.io/gem/v/openfeature-sdk?style=for-the-badge\&color=red)](https://rubygems.org/gems/openfeature-sdk)[![Code Coverage](https://img.shields.io/codecov/c/github/open-feature/ruby-sdk?style=for-the-badge)](https://codecov.io/gh/open-feature/ruby-sdk)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/9337/badge)](https://bestpractices.coreinfrastructure.org/projects/9337) ## Quick start ### Requirements | Supported Ruby Version | OS | | ---------------------- | --------------------- | | Ruby 3.4.x | Windows, MacOS, Linux | | Ruby 4.0.x | Windows, MacOS, Linux | This project supports all Ruby versions in active maintenance per the [Ruby maintenance schedule](https://www.ruby-lang.org/en/downloads/branches/). ### Install Install the gem and add to the application's Gemfile by executing: ```sh bundle add openfeature-sdk ``` If bundler is not being used to manage dependencies, install the gem by executing: ```sh gem install openfeature-sdk ``` ### Usage ```ruby require 'open_feature/sdk' # API Initialization and configuration OpenFeature::SDK.configure do |config| # your provider of choice, which will be used as the default provider config.set_provider(OpenFeature::SDK::Provider::InMemoryProvider.new( { "flag1" => true, "flag2" => 1 } )) end # Create a client client = OpenFeature::SDK.build_client # fetching boolean value feature flag bool_value = client.fetch_boolean_value(flag_key: 'boolean_flag', default_value: false) # a details method is also available for more information about the flag evaluation # see `EvaluationDetails` for more info bool_details = client.fetch_boolean_details(flag_key: 'boolean_flag', default_value: false) # fetching string value feature flag string_value = client.fetch_string_value(flag_key: 'string_flag', default_value: 'default') # fetching number value feature flag float_value = client.fetch_number_value(flag_key: 'number_value', default_value: 1.0) integer_value = client.fetch_number_value(flag_key: 'number_value', default_value: 1) # get an object value object = client.fetch_object_value(flag_key: 'object_value', default_value: { name: 'object'}) ``` ## Features | Status | Features | Description | | ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations for experimentation. | | ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md) for a transaction (e.g. an HTTP request or a thread) | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Ruby) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```ruby OpenFeature::SDK.configure do |config| # your provider of choice, which will be used as the default provider config.set_provider(OpenFeature::SDK::Provider::InMemoryProvider.new( { "v2_enabled" => true, } )) end ``` #### Blocking Provider Registration If you need to ensure that a provider is fully initialized before continuing, you can use `set_provider_and_wait`: ```ruby # Using the SDK directly begin OpenFeature::SDK.set_provider_and_wait(my_provider) puts "Provider is ready!" rescue OpenFeature::SDK::ProviderInitializationError => e puts "Provider failed to initialize: #{e.message}" puts "Error code: #{e.error_code}" # original_error contains the underlying exception that caused the initialization failure puts "Original error: #{e.original_error}" end # With custom timeout (default is 30 seconds) OpenFeature::SDK.set_provider_and_wait(my_provider, timeout: 60) # Domain-specific provider OpenFeature::SDK.set_provider_and_wait(my_provider, domain: "feature-flags") # Via configuration block OpenFeature::SDK.configure do |config| begin config.set_provider_and_wait(my_provider) rescue OpenFeature::SDK::ProviderInitializationError => e # Handle initialization failure end end ``` The `set_provider_and_wait` method: * Waits for the provider's `init` method to complete successfully * Raises `ProviderInitializationError` if initialization fails or times out * Provides access to the provider instance, error code, and original exception for debugging * The `original_error` field contains the underlying exception that caused the initialization failure * Uses the same thread-safe provider switching as `set_provider` In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [domains](#domains), which is covered in more detail below. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```ruby OpenFeature::SDK.configure do |config| # you can set a global evaluation context here config.evaluation_context = OpenFeature::SDK::EvaluationContext.new("host" => "myhost.com") end # Evaluation context can be set on a client as well client_with_context = OpenFeature::SDK.build_client( evaluation_context: OpenFeature::SDK::EvaluationContext.new("controller_name" => "admin") ) # Invocation evaluation context can also be passed in during flag evaluation. # During flag evaluation, invocation context takes precedence over client context # which takes precedence over API (aka global) context. bool_value = client.fetch_boolean_value( flag_key: 'boolean_flag', default_value: false, evaluation_context: OpenFeature::SDK::EvaluationContext.new("is_friday" => true) ) ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Ruby) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Hooks can be registered at the global, client, or flag invocation level. ```ruby # Define a hook class MyHook include OpenFeature::SDK::Hooks::Hook def before(hook_context:, hints:) puts "Evaluating flag: #{hook_context.flag_key}" nil end def after(hook_context:, evaluation_details:, hints:) puts "Flag #{hook_context.flag_key} evaluated to: #{evaluation_details.value}" end def error(hook_context:, exception:, hints:) puts "Error evaluating #{hook_context.flag_key}: #{exception.message}" end def finally(hook_context:, evaluation_details:, hints:) puts "Evaluation complete for #{hook_context.flag_key}" end end # Register at the API (global) level OpenFeature::SDK.hooks << MyHook.new # Register at the client level client = OpenFeature::SDK.build_client client.hooks << MyHook.new # Register at the invocation level client.fetch_boolean_value( flag_key: "my-flag", default_value: false, hooks: [MyHook.new] ) ``` ### Logging The SDK includes a built-in `LoggingHook` that provides structured log output for flag evaluations. It logs at the `before`, `after`, and `error` stages of the hook lifecycle. ```ruby # Use the SDK's default logger (from Configuration) OpenFeature::SDK.hooks << OpenFeature::SDK::Hooks::LoggingHook.new # Or provide your own logger logger = Logger.new($stdout) OpenFeature::SDK.hooks << OpenFeature::SDK::Hooks::LoggingHook.new(logger: logger) # Optionally include evaluation context in log output OpenFeature::SDK.hooks << OpenFeature::SDK::Hooks::LoggingHook.new( logger: logger, include_evaluation_context: true ) ``` Log output uses a structured key=value format: * **before** (DEBUG): `stage=before domain=my-domain provider_name=my-provider flag_key=my-flag default_value=false` * **after** (DEBUG): includes `reason`, `variant`, and `value` * **error** (ERROR): includes `error_code` and `error_message` ### Domains Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used. ```ruby OpenFeature::SDK.configure do |config| config.set_provider(OpenFeature::SDK::Provider::NoOpProvider.new, domain: "legacy_flags") end # Create a client for a different domain, this will use the provider assigned to that domain legacy_flag_client = OpenFeature::SDK.build_client(domain: "legacy_flags") ``` ### Eventing Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider. Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`. Please refer to the documentation of the provider you're using to see what events are supported. ```ruby # Register event handlers at the API (global) level ready_handler = ->(event_details) do puts "Provider #{event_details[:provider].metadata.name} is ready!" end OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_READY, ready_handler) # The SDK automatically emits lifecycle events. Providers can emit additional spontaneous events # using the EventEmitter mixin to signal internal state changes like configuration updates. class MyEventAwareProvider include OpenFeature::SDK::Provider::EventEmitter def init(evaluation_context) # Start background process to monitor for configuration changes # Note: SDK automatically emits PROVIDER_READY when init completes successfully start_background_process end def start_background_process Thread.new do # Monitor for configuration changes and emit events when they occur if configuration_changed? emit_event( OpenFeature::SDK::ProviderEvent::PROVIDER_CONFIGURATION_CHANGED, message: "Flag configuration updated" ) end end end end # Remove specific handlers when no longer needed OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_READY, ready_handler) ``` ### Shutdown The OpenFeature API provides a `shutdown` method to perform cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```ruby # Shut down all registered providers and clear state OpenFeature::SDK.shutdown ``` Individual providers can implement a `shutdown` method to perform cleanup: ```ruby class MyProvider def shutdown # Perform any shutdown/reclamation steps with flag management system here end end ``` ### Tracking The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations. This is essential for robust experimentation powered by feature flags. For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function. ```ruby client = OpenFeature::SDK.build_client # Simple tracking event client.track("checkout_completed") # With evaluation context client.track( "purchase", evaluation_context: OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123") ) # With tracking event details (optional numeric value + custom fields) details = OpenFeature::SDK::TrackingEventDetails.new( value: 99.99, plan: "premium", currency: "USD" ) client.track("subscription", tracking_event_details: details) ``` Note that some providers may not support tracking; if the provider does not implement a `track` method, the call is a no-op. Check the documentation for your [provider](#providers) for more information. ### Transaction Context Propagation Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). The SDK ships with a `ThreadLocalTransactionContextPropagator` that stores context in `Thread.current`: ```ruby # Set up the propagator OpenFeature::SDK.set_transaction_context_propagator( OpenFeature::SDK::ThreadLocalTransactionContextPropagator.new ) # Set transaction context (e.g. in a request middleware) OpenFeature::SDK.set_transaction_context( OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123", "email" => "user@example.com") ) # Transaction context is automatically merged during flag evaluation. # Merge precedence: invocation > client > transaction > API (global) client = OpenFeature::SDK.build_client client.fetch_boolean_value(flag_key: "my-flag", default_value: false) ``` You can implement a custom propagator by including the `TransactionContextPropagator` module: ```ruby class MyRequestScopedPropagator include OpenFeature::SDK::TransactionContextPropagator def set_transaction_context(evaluation_context) # Store context in your request-scoped storage RequestStore[:openfeature_context] = evaluation_context end def get_transaction_context RequestStore[:openfeature_context] end end ``` ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/ruby-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `Provider` duck. ```ruby class MyProvider def init # Perform any initialization steps with flag management system here # Return value is ignored # **Note** The OpenFeature spec defines a lifecycle method called `initialize` to be called when a new provider is set. # To avoid conflicting with the Ruby `initialize` method, this method should be named `init` when creating a provider. end def shutdown # Perform any shutdown/reclamation steps with flag management system here # Return value is ignored end def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil) # Retrieve a boolean value from provider source end def fetch_string_value(flag_key:, default_value:, evaluation_context: nil) # Retrieve a string value from provider source end def fetch_number_value(flag_key:, default_value:, evaluation_context: nil) # Retrieve a numeric value from provider source end def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil) # Retrieve a integer value from provider source end def fetch_float_value(flag_key:, default_value:, evaluation_context: nil) # Retrieve a float value from provider source end def fetch_object_value(flag_key:, default_value:, evaluation_context: nil) # Retrieve a hash value from provider source end # Optional: implement tracking support (spec 6.1.4) # If not defined, Client#track is a no-op def track(tracking_event_name, evaluation_context:, tracking_event_details:) # Record a tracking event with your flag management system end end ``` > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/ruby-sdk-contrib) available under the OpenFeature organization. Implement your own hook by including the `OpenFeature::SDK::Hooks::Hook` module. You only need to define the stages you care about — unimplemented stages are no-ops by default. ```ruby class MyLoggingHook include OpenFeature::SDK::Hooks::Hook def before(hook_context:, hints:) puts "Evaluating #{hook_context.flag_key}" nil # Return nil or an EvaluationContext to merge end def after(hook_context:, evaluation_details:, hints:) puts "Result: #{evaluation_details.value}" end # error and finally are optional — only define what you need end ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # OpenFeature Rust SDK [![Specification](https://img.shields.io/static/v1?label=specification\&message=v0.5.2\&color=yellow\&style=for-the-badge)](https://github.com/open-feature/spec/releases/tag/v0.5.2)[![Release](https://img.shields.io/static/v1?label=release\&message=v0.3.0\&color=blue\&style=for-the-badge)](https://github.com/open-feature/rust-sdk/releases/tag/open-feature-v0.3.0)
[![Crates.io](https://img.shields.io/crates/v/open-feature)](https://crates.io/crates/open-feature)[![Min rust version](https://img.shields.io/badge/rust-%3E=1.80.1-blue.svg)](https://www.rust-lang.org/tools/install)[![Repo status](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) ## Quick start ### Requirements This package was built with Rust version `1.80.1`. Earlier versions might work, but is not guaranteed. ### Install Add the following content to the `Cargo.toml` file: ```toml open-feature = "0.3.0" ``` ### Usage ```rust async fn example() -> Result<(), Error> { // Acquire an OpenFeature API instance. // Note the `await` call here because asynchronous lock is used to // guarantee thread safety. let mut api = OpenFeature::singleton_mut().await; // Configure a provider. // By default [`NoOpProvider`] is used. api.set_provider(NoOpProvider::default()).await; // create a client let client = api.create_client(); // get a bool flag value let is_feature_enabled = client .get_bool_value("v2_enabled", None, None) .await .unwrap_or(false); Ok(()) } ``` Note that the default `NoOpProvider` always returns `Err` for any given input. #### Extended Example ```rust #[tokio::test] async fn extended_example() { // Acquire an OpenFeature API instance. let mut api = OpenFeature::singleton_mut().await; // Set the default (unnamed) provider. api.set_provider(NoOpProvider::default()).await; // Create an unnamed client. let client = api.create_client(); // Create an evaluation context. // It supports types mentioned in the specification. let evaluation_context = EvaluationContext::default() .with_targeting_key("Targeting") .with_custom_field("bool_key", true) .with_custom_field("int_key", 100) .with_custom_field("float_key", 3.14) .with_custom_field("string_key", "Hello".to_string()) .with_custom_field("datetime_key", time::OffsetDateTime::now_utc()) .with_custom_field( "struct_key", EvaluationContextFieldValue::Struct(Arc::new(MyStruct::default())), ) .with_custom_field("another_struct_key", Arc::new(MyStruct::default())) .with_custom_field( "yet_another_struct_key", EvaluationContextFieldValue::new_struct(MyStruct::default()), ); // This function returns a `Result`. // You can process it with functions provided by std. let is_feature_enabled = client .get_bool_value("SomeFlagEnabled", Some(&evaluation_context), None) .await .unwrap_or(false); if is_feature_enabled { // Let's get evaluation details. let _result = client .get_int_details("key", Some(&evaluation_context), None) .await; } } ``` #### Getting a Struct from a Provider It is possible to extract a struct from the provider. Internally, this SDK defines a type `StructValue` to store any structure value. The `client.get_struct_value()` functions takes a type parameter `T`. It will try to parse `StructValue` resolved by the provider to `T`, as long as `T` implements trait `TryFrom`. You can pass in a type that satisfies this trait bound. When the conversion fails, it returns an `Err` with `EvaluationErrorCode::TypeMismatch`. ### API Reference See [here](https://docs.rs/open-feature/latest/open_feature/index.html) for the API docs. ## Features | Status | Features | Description | | ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Named clients](#named-clients) | Utilize multiple providers in a single application. | | ❌ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ ### Providers [Providers](https://openfeature.dev/docs/reference/concepts/provider.md) are an abstraction between a flag management system and the OpenFeature SDK. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Rust) for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```rust // Set the default feature provider. Please replace the `NoOpProvider` with the one you want. // If you do not do that, [`NoOpProvider`] will be used by default. // // [`NoOpProvider`] always returns `Err` despite any input. You can use functions like // `unwrap_or()` to specify default values. // // If you set a new provider after creating some clients, the existing clients will pick up // the new provider you just set. // // You must `await` it to let the provider's initialization to finish. let mut api = OpenFeature::singleton_mut().await; api.set_provider(NoOpProvider::default()).await; ``` In some situations, it may be beneficial to register multiple providers in the same application. This is possible using [named clients](#named-clients), which is covered in more detail below. ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary.md#targeting). If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context.md). ```rust // Create a global evaluation context and set it into the API. // Note that this is optional. By default it uses an empty one. let mut api = OpenFeature::singleton_mut().await; api.set_evaluation_context(global_evaluation_context).await; // Set client level evaluation context. // It will overwrite the global one for the existing keys. let mut client = api.create_client(); client.set_evaluation_context(client_evaluation_context); // Pass evaluation context in evaluation functions. // This one will overwrite the global evaluation context and // the client level one. client.get_int_value("flag", Some(&evaluation_context), None); ``` ### Hooks [Hooks](https://openfeature.dev/docs/reference/concepts/hooks.md) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look [here](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook\&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Rust) for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```rust let mut api = OpenFeature::singleton_mut().await; // Add a global hook. api.add_hook(MyHook::default()).await; // Create a client and add a client level hook. let client = api.create_client().with_hook(MyHook::default()); // Get a flag value with a hook. let eval = EvaluationOptions::default().with_hook(MyHook::default()); client.get_int_value("key", None, Some(&eval)).await; ``` Example of a hook implementation you can find in [examples/hooks.rs](https://github.com/open-feature/rust-sdk/blob/main/examples/hooks.rs). To run the example, execute the following command: ```shell cargo run --example hooks ``` ### Logging Note that in accordance with the OpenFeature specification, the SDK doesn't generally log messages during flag evaluation. #### Logging hook The Rust SDK provides a logging hook that can be used to log messages during flag evaluation. This hook is not enabled by default and must be explicitly set. ```rust let mut api = OpenFeature::singleton_mut().await; let client = api.create_client().with_logging_hook(false); ... // Note: You can include evaluation context to log output. let client = api.create_client().with_logging_hook(true); ``` Both **text** and **structured** logging are supported. To enable **structured** logging, enable feature `structured-logging` in your `Cargo.toml`: ```toml open-feature = { version = "0.3.0", features = ["structured-logging"] } ``` Example of a logging hook usage you can find in [examples/logging.rs](https://github.com/open-feature/rust-sdk/blob/main/examples/logging.rs). To run the example, execute the following command: ```shell cargo run --example logging ``` **Output**: ```text [2025-01-10T18:53:11Z DEBUG open_feature::hooks::logging] Before stage: domain=, provider_name=Dummy Provider, flag_key=my_feature, default_value=Some(Bool(false)), evaluation_context=EvaluationContext { targeting_key: None, custom_fields: {} } [2025-01-10T18:53:11Z DEBUG open_feature::hooks::logging] After stage: domain=, provider_name=Dummy Provider, flag_key=my_feature, default_value=Some(Bool(false)), reason=None, variant=None, value=Bool(true), evaluation_context=EvaluationContext { targeting_key: None, custom_fields: {} } ``` or with structured logging: ```shell cargo run --example logging --features structured-logging ``` **Output**: ```jsonl {"default_value":"Some(Bool(false))","domain":"","evaluation_context":"EvaluationContext { targeting_key: None, custom_fields: {} }","flag_key":"my_feature","level":"DEBUG","message":"Before stage","provider_name":"No-op Provider","target":"open_feature","timestamp":1736537120828} {"default_value":"Some(Bool(false))","domain":"","error_message":"Some(\"No-op provider is never ready\")","evaluation_context":"EvaluationContext { targeting_key: None, custom_fields: {} }","file":"src/hooks/logging.rs","flag_key":"my_feature","level":"ERROR","line":162,"message":"Error stage","module":"open_feature::hooks::logging::structured","provider_name":"No-op Provider","target":"open_feature","timestamp":1736537120828} ``` ### Named clients Clients can be given a name. A name is a logical identifier that can be used to associate clients with a particular provider. If a name has no associated provider, the global provider is used. ```rust // Create a named provider and bind it. api.set_named_provider("named", NoOpProvider::default()).await; // This named client will use the feature provider bound to this name. let client = api.create_named_client("named"); assert_eq!(client.get_int_value("key", None, None).await.unwrap(), 42); ``` ### Eventing Events are not yet available in the Rust SDK. ### Shutdown The OpenFeature API provides a close function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down. ```rust // This will clean all the registered providers and invoke their `shutdown()` function. let api = OpenFeature::singleton_mut().await; api.shutdown().await; ``` ## Extending ### Develop a provider To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/rust-sdk-contrib) available under the OpenFeature organization. You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. Check the source of [`NoOpProvider`](https://github.com/open-feature/rust-sdk/blob/main/src/provider/no_op_provider.rs) for an example. > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=provider\&projects=\&template=document-provider.yaml\&title=%5BProvider%5D%3A+) so we can add it to the docs! ### Develop a hook To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/rust-sdk-contrib) available under the OpenFeature organization. Implement your own hook by conforming to the `Hook interface`. To satisfy the interface, all methods (`before`/`after`/`finally`/`error`) need to be defined. ```rust use open_feature::{ EvaluationContext, EvaluationDetails, EvaluationError, Hook, HookContext, HookHints, Value, }; struct MyHook; #[async_trait::async_trait] impl Hook for MyHook { async fn before<'a>( &self, context: &HookContext<'a>, hints: Option<&'a HookHints>, ) -> Result, EvaluationError> { todo!() } async fn after<'a>( &self, context: &HookContext<'a>, details: &EvaluationDetails, hints: Option<&'a HookHints>, ) -> Result<(), EvaluationError> { todo!() } async fn error<'a>( &self, context: &HookContext<'a>, error: &EvaluationError, hints: Option<&'a HookHints>, ) { todo!() } async fn finally<'a>( &self, context: &HookContext<'a>, evaluation_details: &EvaluationDetails, hints: Option<&'a HookHints>, ) { todo!() } } ``` > Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=\&labels=hook\&projects=\&template=document-hook.yaml\&title=%5BHook%5D%3A+) so we can add it to the docs! --- # Five Minutes to Feature Flags We’re going to add feature flagging to a Node service in under five minutes using OpenFeature, the open, vendor-agnostic feature flagging SDK. This tutorial uses a simple [express](https://expressjs.com/) server, but if you're familiar with JavaScript and Node you should be able to follow along. > Prefer the zero-install, in-browser option? [Run this tutorial in-browser using Killercoda](https://killercoda.com/open-feature/scenario/five-minutes-to-feature-flags). ## Setup Before we jump into the tutorial, let's quickly get everything setup. You'll need [Git](https://git-scm.com/) and [NodeJS](https://nodejs.org/) version 16 or newer. After that, run the following command from your favorite terminal. ```bash git clone https://github.com/open-feature/five-minutes-to-feature-flags.git && \ cd five-minutes-to-feature-flags && \ npm install ``` ## Hello, world Here’s the service we’ll be working on: 01_vanilla.js ```js import express from 'express'; import Router from 'express-promise-router'; const app = express(); const routes = Router(); app.use((_, res, next) => { res.setHeader('content-type', 'text/plain'); next(); }, routes); routes.get('/', async (_, res) => { res.send('Hello, world!'); }); app.listen(3333, () => { console.log('Server running at http://localhost:3333'); }); ``` Pretty much the most basic express server you can imagine - a single endpoint at `/` that returns a plaintext “Hello, world!” response. Let's start the service by running: ```bash node 01_vanilla.js ``` Now you can test to make sure it works by entering the following command into a second terminal: ```bash curl http://localhost:3333 ``` The output should look like this: ```disable-copy-button Hello, world! ``` Looks good! Go ahead and stop the server using `Ctrl` + `C`. ## With cows, please Let’s imagine that we’re adding a new, experimental feature to this hello world service. We’re going to upgrade the format of the server’s response, using [cowsay](https://www.npmjs.com/package/cowsay)! We are, however, not 100% sure that this cowsay formatting is going to work out, so for now we’ll protect it behind a conditional: 02_basic_flags.js ```js import express from 'express'; import Router from 'express-promise-router'; import cowsay from 'cowsay'; const app = express(); const routes = Router(); app.use((_, res, next) => { res.setHeader('content-type', 'text/plain'); next(); }, routes); routes.get('/', async (_, res) => { // set this to true to test our new // cow-based greeting system const withCow = false; if (withCow) { res.send(cowsay.say({ text: 'Hello, world!' })); } else { res.send('Hello, world!'); } }); app.listen(3333, () => { console.log('Server running at http://localhost:3333'); }); ``` Now let's start the server with our basic flag configuration by running: ```bash node 02_basic_flags.js ``` By default, the service continues to work exactly as it did before. Next change `withCow` to `true` using your favorite text editor and restart the node server. Now the response comes in an exciting new format. To try it out, enter the following command into the terminal: ```bash curl http://localhost:3333 ``` The output should look like this: ```disable-copy-button _______________ < Hello, world! > --------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` tip A server restart is required for any changes to be applied. ## The crudest flag That `withCow` boolean and its accompanying conditional check are a very basic feature flag - they let us hide an experimental or unfinished feature, but also easily switch the feature on while we’re building and testing it. ## Introducing OpenFeature Managing these flags by changing hardcoded constants gets old fast though. A team that uses feature flags in any significant way soon reaches for a feature flagging framework. Let’s move in that direction by updating the service to use OpenFeature. 03_openfeature.js ```js import express from 'express'; import Router from 'express-promise-router'; import cowsay from 'cowsay'; import { OpenFeature } from '@openfeature/server-sdk'; const app = express(); const routes = Router(); app.use((_, res, next) => { res.setHeader('content-type', 'text/plain'); next(); }, routes); const featureFlags = OpenFeature.getClient(); routes.get('/', async (_, res) => { const withCows = await featureFlags.getBooleanValue('with-cows', false); if (withCows) { res.send(cowsay.say({ text: 'Hello, world!' })); } else { res.send('Hello, world!'); } }); app.listen(3333, () => { console.log('Server running at http://localhost:3333'); }); ``` We’ve imported the `@openfeature/server-sdk` NPM module, and used it to create an OpenFeature client called `featureFlags`. We then call `getBooleanValue` on that client to find out if the `with-cows` feature flag is true or false. Depending on what we get back we either show the new cow-based output, or the traditional plaintext format. Start the server with: ```bash node 03_openfeature.js ``` Enter the following command into the terminal: ```bash curl http://localhost:3333 ``` The output should look like this: ```disable-copy-button Hello, world! ``` note When we call `getBooleanValue` we also provide a default value of false. Since we haven’t configured the OpenFeature SDK with a *feature flag provider* yet, it will always return that default value. ## Configuring an OpenFeature Provider OpenFeature becomes useful when we connect our OpenFeature SDK to a full-fledged feature flagging system - a commercial product, an open-source offering, or perhaps a custom internal tool - so that it can provide flagging decisions from that system. Connecting OpenFeature to one of these backends is very straightforward, but it does require that we have an actual flagging framework set up. For now, let’s just configure a really, really simple provider that doesn’t need a backend: 04_openfeature_with_provider.js ```js import express from "express"; import Router from "express-promise-router"; import cowsay from "cowsay"; import { OpenFeature, InMemoryProvider } from "@openfeature/server-sdk"; const app = express(); const routes = Router(); app.use((_, res, next) => { res.setHeader("content-type", "text/plain"); next(); }, routes); const featureFlags = OpenFeature.getClient(); const FLAG_CONFIGURATION = { 'with-cows': { variants: { on: true, off: false }, disabled: false, defaultVariant: "on" } }; const featureFlagProvider = new InMemoryProvider(FLAG_CONFIGURATION); OpenFeature.setProvider(featureFlagProvider); routes.get("/", async (_, res) => { const withCows = await featureFlags.getBooleanValue("with-cows", false); if (withCows) { res.send(cowsay.say({ text: "Hello, world!" })); } else { res.send("Hello, world!"); } }); app.listen(3333, () => { console.log("Server running at http://localhost:3333"); }); ``` This minimalist provider is exactly that; you give it a hard-coded set of feature flag values, and it provides those values via the OpenFeature SDK. In our `FLAG_CONFIGURATION` above we’ve hard-coded that `with-cows` feature flag to true, which means that conditional predicate in our express app will now evaluate to true, which means that our service will now start providing bovine output. Start the server with: ```bash node 04_openfeature_with_provider.js ``` Test it out by entering the following command into the terminal: ```bash curl http://localhost:3333 ``` The output should look like this: ```disable-copy-button _______________ < Hello, world! > --------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` Next change the `with-cows` value to false and restart the node server. We will now see the more boring response when entering the following command into the terminal: ```bash curl http://localhost:3333 ``` The output should look like this: ```disable-copy-button Hello, world! ``` ## Configuring targeting Feature flags are at their most powerful when we can use contextual information to determine feature flag values. We call this targeting. We'll set the `defaultVariant` back to "off" to make sure our targeting works. Now, let's add some targeting by adding a `contextEvaluator` to the `"with-cows"` flag. We'll use some request data as the basis of or flag evaluation - let's check the `X-Cow` HTTP header. Then, when we evaluate the flag, will be sure to pass our additional contextual information. 05_openfeature_with_targeting.js ```js import express from "express"; import Router from "express-promise-router"; import cowsay from "cowsay"; import { OpenFeature, InMemoryProvider } from "@openfeature/server-sdk"; const app = express(); const routes = Router(); app.use((_, res, next) => { res.setHeader("content-type", "text/plain"); next(); }, routes); const featureFlags = OpenFeature.getClient(); const FLAG_CONFIGURATION = { 'with-cows': { variants: { on: true, off: false }, disabled: false, defaultVariant: "off", contextEvaluator: (context) => { if (context.cow === "Bessie") { return "on"; } return "off"; }, } }; const featureFlagProvider = new InMemoryProvider(FLAG_CONFIGURATION); OpenFeature.setProvider(featureFlagProvider); routes.get("/", async (req, res) => { const context = { cow: req.get("x-cow") }; const withCows = await featureFlags.getBooleanValue("with-cows", false, context); if (withCows) { res.send(cowsay.say({ text: "Hello, world!" })); } else { res.send("Hello, world!"); } }); app.listen(3333, () => { console.log("Server running at http://localhost:3333"); }); ``` Start the node server: ```bash node 05_openfeature_with_targeting.js ``` Make a new request: ```bash curl http://localhost:3333 ``` The output should look like this: ```disable-copy-button Hello, world! ``` Now, make a request with our specific cow of interest: ```bash curl http://localhost:3333 -H "X-Cow: Bessie" ``` The output should once again look like this: ```disable-copy-button _______________ < Hello, world! > --------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` note This is a simple targeting mechanism used by our InMemory provider. Other systems provide more robust targeting capabilities. ## Moving to a full feature-flagging system We’ve gotten started with OpenFeature using a very simple but extremely limited provider. The beauty of OpenFeature is that we can transition to a real feature-flagging system when we’re ready, without any change to how we evaluate flags. It’s as simple as configuring a [different provider](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider). We can get started with feature flags with low investment and low risk, and once we’re ready, it’s just a few lines of code to transition to a full-featured, scalable backend. ## Next steps To learn more about OpenFeature, check out the documentation [here](https://openfeature.dev/docs/reference/intro.md). Specifically, you can read more about how the [evaluation API works](https://openfeature.dev/docs/reference/concepts/evaluation-api.md), what [tech stacks are supported](https://openfeature.dev/docs/reference/sdks.md), or read [more tutorials](https://openfeature.dev/docs/category/getting-started) about using OpenFeature in a variety of tech stacks. More hands-on tutorials are available on the [OpenFeature Killercoda page](https://killercoda.com/open-feature). We strive to provide a welcoming, open community. If you have any questions - or just want to nerd out about feature flags - the `#OpenFeature` channel in the [CNCF Slack](https://slack.cncf.io/) is the place for you. --- # Getting Started with the OpenFeature .NET SDK ## Introduction This walk-through teaches you the basics of using OpenFeature in .NET within a ASP.NET Core Web application. You'll learn how to: * Integrate the OpenFeature .NET SDK * Install and configure the OpenFeature provider * Perform basic feature flagging ## Requirements This walk-through assumes that: * You have a basic knowledge of C# and .NET * You have installed the [.NET 8](https://dotnet.microsoft.com/en-us/) or later SDK * You have Docker installed and running on the host system ## Walk-through ### Step 1: Create a .NET 8 Web application To get started you can use the .NET SDK to initialise a web application. Open a terminal (**shell**, **Command Prompt**, or **bash**) and paste the following commands: ```shell dotnet new webapi -o openfeature-dotnet-sample cd openfeature-dotnet-sample dotnet run ``` ### Step 2: Add dependencies With NuGet you can install the latest [OpenFeature](https://www.nuget.org/packages/OpenFeature) package into your .NET web application. ```shell dotnet add package OpenFeature dotnet add package OpenFeature.Hosting ``` ### Step 3: Add code The following will initialise an `InMemoryProvider` for use within the web application. Open a code editor and add the C# code below to the Program.cs. ```csharp + using Microsoft.AspNetCore.Mvc; + using OpenFeature; + using OpenFeature.Hosting.Providers.Memory; + using OpenFeature.Providers.Memory; var builder = WebApplication.CreateBuilder(args); // Add services to the container. // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); + // Register your feature flag provider + builder.Services.AddOpenFeature(featureBuilder => + { + featureBuilder + .AddInMemoryProvider(_ => new Dictionary() + { + { + "welcome-message", new Flag( + new Dictionary { { "show", true }, { "hide", false } }, "show") + } + }); + }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); - var summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - app.MapGet("/weatherforecast", () => - { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; - }) - .WithName("GetWeatherForecast"); + app.MapGet("/hello", async ([FromServices] IFeatureClient featureClient) => + { + if (await featureClient.GetBooleanValueAsync("welcome-message", false)) + { + return "Hello, welcome to this OpenFeature-enabled website!"; + } + + return "Hello!"; + }) + .WithName("GetHello"); app.Run(); - record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) - { - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - } ``` At this point, we are ready to run the initial version of our application. ### Step 4: Run the initial application Let's compile and run the application. ```shell dotnet build dotnet run ``` In the logs you should see a line with the following `Now listening on: http://localhost:5251`, although the port number may differ. You can visit the following URL in your browser (adjust port number as necessary) to view the hello world message. You should see the message "Hello!". "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. ### Step 5: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Before we can use the Flagd provider in .NET we need to install the [.NET OpenFeature Flagd](https://www.nuget.org/packages/OpenFeature.Contrib.Providers.Flagd/) package. ```shell dotnet add package OpenFeature.Contrib.Providers.Flagd ``` Finally, let's add the required code change to enable the flagd provider in our .NET application. ```csharp using Microsoft.AspNetCore.Mvc; using OpenFeature; + using OpenFeature.DependencyInjection.Providers.Flagd; - using OpenFeature.Providers.Memory; - using OpenFeature.Hosting.Providers.Memory; var builder = WebApplication.CreateBuilder(args); // Add services to the container. // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); var app = builder.Build(); // Register your feature flag provider builder.Services.AddOpenFeature(featureBuilder => { featureBuilder .AddInMemoryProvider(_ => new Dictionary() { { "welcome-message", new Flag( new Dictionary { { "show", true }, { "hide", false } }, "show") } }); + .AddFlagdProvider(); }); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); app.MapGet("/hello", async ([FromServices] IFeatureClient featureClient) => { if (await featureClient.GetBooleanValueAsync("welcome-message", false)) { return "Hello, welcome to this OpenFeature-enabled website!"; } return "Hello!"; }) .WithName("GetHello"); app.Run(); ``` ### Step 6: Rerun the application We can use the .NET CLI to build and run our application. ```shell dotnet build dotnet run ``` You can visit the following URL in your browser (adjust port number as necessary) to view the hello world message. You should see the message "Hello!". Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Revisit the endpoint and you will be greeted with `Hello, welcome to this OpenFeature-enabled website!` ## Conclusion This walk-through introduced you to the OpenFeature .NET SDK. It covered how a provider can be configured to perform the flag evaluation and introduced basic feature flagging concepts. It also showcased how feature flags can be updated at runtime, without requiring a code change and a redeployment. --- # Getting Started with the OpenFeature Go SDK and Go Gin ## Introduction This walk-through teaches you the basics of using OpenFeature in Go in the context of a Go Gin web application. You'll learn how to: * Integrate the OpenFeature Go SDK * Install and configure the OpenFeature provider * Perform basic feature flagging ## Requirements ## Walk-through This walk-through assumes that: * You have a basic knowledge of Go * You have Go 1.17 or later * You have Docker installed and running on the host system ### Step 1: Create a new Go project To get started, let's create a new folder and initialize the project. This can be done by running following commands. ```shell mkdir gostart cd gostart go mod init gostart ``` ### Step 2: Set up the application Let's initialize the application with logic to expose a minimal Go Gin web endpoint. First, create the file `main.go` and include the following code. ```go package main import ( "github.com/gin-gonic/gin" "net/http" ) const defaultMessage = "Hello!" func main() { // Initialize Go Gin engine := gin.Default() // Setup a simple endpoint engine.GET("/hello", func(c *gin.Context) { c.JSON(http.StatusOK, defaultMessage) }) engine.Run() } ``` To add Go Gin dependency to the application, run the following commands. ```shell go get github.com/gin-gonic/gin go mod tidy ``` ### Step 3: Add the OpenFeature SDK Next, let's add logic to customize the response message based on the `boolean` [flag key](https://openfeature.dev/specification/glossary.md#flag-key) `welcome-message`. ```go package main import ( + "context" "github.com/gin-gonic/gin" + "github.com/open-feature/go-sdk/openfeature" "net/http" ) const defaultMessage = "Hello!" + const newWelcomeMessage = "Hello, welcome to this OpenFeature-enabled website!" func main() { + // Initialize OpenFeature client + client := openfeature.NewClient("GoStartApp") // Initialize Go Gin engine := gin.Default() // Setup a simple endpoint engine.GET("/hello", func(c *gin.Context) { - c.JSON(http.StatusOK, defaultMessage) - + // Evaluate welcome-message feature flag + welcomeMessage, _ := client.BooleanValue( + context.Background(), "welcome-message", false, openfeature.EvaluationContext{}, + ) + + if welcomeMessage { + c.JSON(http.StatusOK, newWelcomeMessage) + return + } else { + c.JSON(http.StatusOK, defaultMessage) + return + } }) engine.Run() } ``` To add OpenFeature SDK dependency to the application, run the following commands. ```shell go get github.com/open-feature/go-sdk/openfeature go mod tidy ``` ### Step 4: Run the initial application Let's start the application and see it in action. ```shell go run main.go ``` Now you can visit the url and observe the message **Hello!**. "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. ### Step 5: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Now, let's make the required code changes in our application. ```go + import ( + "context" + "github.com/gin-gonic/gin" + flagd "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg" + "log" "github.com/open-feature/go-sdk/openfeature" "net/http" ) func main() { + // Use flagd as the OpenFeature provider + provider, err := flagd.NewProvider() + if err != nil { + log.Fatalf("failed to initialize flagd provider: %v", err) + } + if err := openfeature.SetProviderAndWait(provider); err != nil { + // If a provider initialization error occurs, log it and exit + log.Fatalf("Failed to set the OpenFeature provider: %v", err) + } // Initialize OpenFeature client client := openfeature.NewClient("GoStartApp") ``` Then, let's add `flagd provider` dependency with following commands. ```shell go get github.com/open-feature/go-sdk-contrib/providers/flagd go mod tidy ``` The complete `main.go` file is given below: ```go package main import ( "context" "log" "net/http" "github.com/gin-gonic/gin" flagd "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg" "github.com/open-feature/go-sdk/openfeature" ) const defaultMessage = "Hello!" const newWelcomeMessage = "Hello, welcome to this OpenFeature-enabled website!" func main() { // Use flagd as the OpenFeature provider provider, err := flagd.NewProvider() if err != nil { log.Fatalf("failed to initialize flagd provider: %v", err) } if err := openfeature.SetProviderAndWait(provider); err != nil { // If an error occurs, log it and exit log.Fatalf("Failed to set the OpenFeature provider: %v", err) } // Initialize OpenFeature client client := openfeature.NewClient("GoStartApp") // Initialize Go Gin engine := gin.Default() // Setup a simple endpoint engine.GET("/hello", func(c *gin.Context) { // Evaluate welcome-message feature flag welcomeMessage, _ := client.BooleanValue( context.Background(), "welcome-message", false, openfeature.EvaluationContext{}, ) if welcomeMessage { c.JSON(http.StatusOK, newWelcomeMessage) return } else { c.JSON(http.StatusOK, defaultMessage) return } }) engine.Run() } ``` ### Step 6: Rerun the application Let's rerun the application with the following command. ```shell go run main.go ``` Revisit the endpoint and you will see the same value. Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Revisit the endpoint and you will be greeted with `Hello, welcome to this OpenFeature-enabled website!` ## Conclusion This walk-through introduced you to the OpenFeature Go SDK and how it can be easily integrated into well-known frameworks such as Go Gin. It covered how a provider can be configured to perform the flag evaluation and introduced basic feature flagging concepts. It also showcased how feature flags can be updated at runtime, without requiring a code change and a redeployment. --- # Getting Started with the OpenFeature Java SDK and Dropwizard ## Introduction This walk-through teaches you the basics of using OpenFeature in Java in the context of a Dropwizard application. You'll learn how to: * Integrate the OpenFeature Java SDK * Install and configure the OpenFeature provider * Perform basic feature flagging ## Requirements This walk-through assumes that: * You have a basic knowledge of Java and Dropwizard * You have Java 8 or later * You have Docker installed and running on the host system This guide uses Java 21 syntax, adjustments might be needed for earlier Java versions. ## Walk-through ### Step 1: Create a Dropwizard application Create a new Dropwizard application following the guide on [dropwizard.io](https://www.dropwizard.io/en/stable/getting-started.html). For this example we are using the maven archetype ```shell $ mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=4.0.13 ... Define value for property 'name': OpenFeatureExample ... Define value for property 'groupId': dev.openfeature Define value for property 'artifactId': openfeature-example Define value for property 'version' 1.0-SNAPSHOT: Define value for property 'package' dev.openfeature: Confirm properties configuration: name: OpenFeatureExample description: null shaded: true groupId: dev.openfeature artifactId: openfeature-example version: 1.0-SNAPSHOT package: dev.openfeature Y: Y ... ``` We now have an application we can open ```shell cd openfeature-example ``` ### Step 2: Add dependencies For dropwizard we can either use the SDK directly, or use a community module [dropwizard-openfeature](https://github.com/sideshowcoder/dropwizard-openfeature), and while dropwizard-openfeature provides some benefits like built-in healthchecks, and managing the startup and shutdown of resources associated with the OpenFeature Java SDK, it is not however not officially supported, so use at your own risk. Depending what you choose add the following to your `pom.xml` **sdk-only** ```xml dev.openfeature sdk 1.23.0 dev.openfeature.contrib.providers flagd 0.11.8 ``` **dropwizard-openfeature** ```xml io.github.sideshowcoder dropwizard-openfeature 1.0.1 ``` ### Step 3: Create a resource We create a resource to serve the `/welcome` endpoint. Depending on the value of the feature flag named `"welcome-message"`, it serves different messages. ```java @Path("/welcome") @Produces(TEXT_PLAIN) public class WelcomeResource { private final Client client; public WelcomeResource(Client client) { this.client = client; } @GET public String getWelcome() { if(client.getBooleanValue("welcome-message", false)) { return "Welcome to OpenFeature in Dropwizard!\n"; } return "Welcome!\n"; } } ``` to make this resource available we need to register it, and inject the OpenFeature `Client`. ```java public class OpenFeatureExampleApplication extends Application { public static void main(final String[] args) throws Exception { new OpenFeatureExampleApplication().run(args); } @Override public String getName() { return "OpenFeatureExample"; } @Override public void initialize(final Bootstrap bootstrap) { // TODO: application initialization } @Override public void run(final OpenFeatureExampleConfiguration configuration, final Environment environment) { - // TODO: implement application + var client = OpenFeatureAPI.getInstance().getClient("dev.openfeature.OpenFeatureExample"); + var welcomeResource = new WelcomeResource(client); + environment.jersey().register(welcomeResource); } } ``` ### Step 4: Run the application Now we can build and run the initial version of the application. ```shell $ mvn clean package $ java -jar target/openfeature-example-1.0-SNAPSHOT.jar server config.yml INFO [2025-06-13 10:00:00,000] io.dropwizard.core.server.DefaultServerFactory: Registering jersey handler with root path prefix: / INFO [2025-06-13 10:00:00,000] io.dropwizard.core.server.DefaultServerFactory: Registering admin handler with root path prefix: / INFO [2025-06-13 10:00:00,000] io.dropwizard.core.server.ServerFactory: Starting OpenFeatureExample ================================================================================ OpenFeatureExample ================================================================================ INFO [2025-06-13 10:00:00,000] org.eclipse.jetty.setuid.SetUIDListener: Opened application@748e9b20{HTTP/1.1, (http/1.1)}{0.0.0.0:8080} INFO [2025-06-13 10:00:00,000] org.eclipse.jetty.setuid.SetUIDListener: Opened admin@2063c53e{HTTP/1.1, (http/1.1)}{0.0.0.0:8081} INFO [2025-06-13 10:00:00,000] org.eclipse.jetty.server.Server: jetty-11.0.25; built: 2025-03-13T00:15:57.301Z; git: a2e9fae3ad8320f2a713d4fa29bba356a99d1295; jvm 21+35 INFO [2025-06-13 10:00:00,000] io.dropwizard.jersey.DropwizardResourceConfig: The following paths were found for the configured resources: GET /welcome (com.sideshowcoder.resources.WelcomeResource) ... ``` In the output we can see the resource being available under `/welcome` as well as the application listening to `http://0.0.0.0:8080`, where `0.0.0.0` refers to all interfaces. Using `curl` we can see the application working. ```shell $ curl -i http://localhost:8080/welcome HTTP/1.1 200 OK Date: Fri, 13 Jun 2025 10:00:00 GMT Content-Type: text/plain Vary: Accept-Encoding Content-Length: 9 Welcome! ``` "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. ### Step 5: Configure flagd as a provider Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Now we configure flagd as the provider in our application **sdk-only** ```java public class OpenFeatureExampleApplication extends Application { public static void main(final String[] args) throws Exception { new OpenFeatureExampleApplication().run(args); } @Override public String getName() { return "OpenFeatureExample"; } @Override public void initialize(final Bootstrap bootstrap) { // nothing to do here } @Override public void run(final OpenFeatureExampleConfiguration configuration, final Environment environment) { + // Use flagd as the OpenFeature provider and use default configurations + try { + OpenFeatureAPI.getInstance().setProviderAndWait(new FlagdProvider()); + } catch (OpenFeatureError e) { + throw new RuntimeException("Failed to set OpenFeature provider", e); + } var client = OpenFeatureAPI.getInstance().getClient("dev.openfeature.OpenFeatureExample"); var welcomeResource = new WelcomeResource(client); environment.jersey().register(welcomeResource); } } ``` **dropwizard-openfeature** Add the bundle configuration to the existing application configuration ```java - public class OpenFeatureExampleConfiguration extends Configuration { + public class OpenFeatureExampleConfiguration extends Configuration implements OpenFeatureBundleConfiguration { - // TODO: implement service configuration + @Valid + @NotNull + @JsonProperty + private OpenFeatureConfiguration openfeature; + + @Override + public OpenFeatureConfiguration getOpenFeatureConfiguration() { + return openfeature; + } } ``` add the bundle configuration to the `config.yml` file ```yaml --- logging: level: INFO loggers: dev.openfeature: DEBUG + openfeature: + provider: flagd ``` initialize the bundle ```java public class OpenFeatureExampleApplication extends Application { public static void main(final String[] args) throws Exception { new OpenFeatureExampleApplication().run(args); } @Override public String getName() { return "OpenFeatureExample"; } @Override public void initialize(final Bootstrap bootstrap) { + bootstrap.addBundle(new OpenFeatureBundle()); } @Override public void run(final OpenFeatureExampleConfiguration configuration, final Environment environment) { var client = OpenFeatureAPI.getInstance().getClient("dev.openfeature.OpenFeatureExample"); var welcomeResource = new WelcomeResource(client); environment.jersey().register(welcomeResource); } } ``` > NOTE: dropwizard-openfeature not only configures the provider, but also adds a healthcheck and hooks the provider into the application startup and shutdown lifecycle. ### Step 6: Rerun the application We can now rerun the application ```shell $ mvn clean package $ java -jar target/openfeature-example-1.0-SNAPSHOT.jar server config.yml INFO [2025-06-13 10:00:00,000] io.dropwizard.core.server.DefaultServerFactory: Registering jersey handler with root path prefix: / INFO [2025-06-13 10:00:00,000] io.dropwizard.core.server.DefaultServerFactory: Registering admin handler with root path prefix: / INFO [2025-06-13 10:00:00,000] io.dropwizard.core.server.ServerFactory: Starting OpenFeatureExample ================================================================================ OpenFeatureExample ================================================================================ INFO [2025-06-13 10:00:00,000] org.eclipse.jetty.setuid.SetUIDListener: Opened application@748e9b20{HTTP/1.1, (http/1.1)}{0.0.0.0:8080} INFO [2025-06-13 10:00:00,000] org.eclipse.jetty.setuid.SetUIDListener: Opened admin@2063c53e{HTTP/1.1, (http/1.1)}{0.0.0.0:8081} INFO [2025-06-13 10:00:00,000] org.eclipse.jetty.server.Server: jetty-11.0.25; built: 2025-03-13T00:15:57.301Z; git: a2e9fae3ad8320f2a713d4fa29bba356a99d1295; jvm 21+35 INFO [2025-06-13 10:00:00,000] io.dropwizard.jersey.DropwizardResourceConfig: The following paths were found for the configured resources: GET /welcome (com.sideshowcoder.resources.WelcomeResource) ... ``` Using `curl` to again fetch the `/welcome` resource will show again the default message ```shell $ curl -i http://localhost:8080/welcome HTTP/1.1 200 OK Date: Fri, 13 Jun 2025 10:00:00 GMT Content-Type: text/plain Vary: Accept-Encoding Content-Length: 9 Welcome! ``` Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` fetching `/welcome` now will show the message for `"welcome-message"` being `true`. ```shell $ curl -i http://localhost:8080/welcome HTTP/1.1 200 OK Date: Fri, 13 Jun 2025 10:00:00 GMT Content-Type: text/plain Vary: Accept-Encoding Content-Length: 38 Welcome to OpenFeature in Dropwizard! ``` ## Conclusion In this walkthrough we learned how to integrate OpenFeature into a Dropwizard application, using flagd to provide the feature flags at runtime. We saw how changing the flags definition can change the runtime behaviour of our application, without the need to redeploy or restart the application. --- # Getting Started with the OpenFeature Java SDK and Spring Boot ## Introduction This walk-through teaches you the basics of using OpenFeature in Java in the context of a Spring Boot Web application. You'll learn how to: * Integrate the OpenFeature Java SDK * Install and configure the OpenFeature provider * Perform basic feature flagging ## Requirements This walk-through assumes that: * You have a basic knowledge of Java and Spring Boot * You have Java 8 or later * You have Docker installed and running on the host system ## Walk-through ### Step 1: Create a Spring Boot Web application To get started, visit [Spring Initializer](https://start.spring.io/). This website allows you to download a pre-configured Spring boot application. For our use case, add **Spring Web** from the dependencies section and set the following **project metadata** values, * Group: com * Artifact: demo Select a Java version matching your installation and download the application using the **GENERATE** button. Extract the archive and open it in your favorite editor. ### Step 2: Add dependencies Based on the selected build system for Spring Boot application, add OpenFeature Java SDK and flagd provider dependencies to existing dependency management configuration. Given below are dependencies for Maven and Gradle with the latest dependency versions at the time of writing this walk-through. **Maven** ```xml dev.openfeature sdk 1.23.0 dev.openfeature.contrib.providers flagd 0.11.20 ``` **Gradle** ```groovy implementation 'dev.openfeature:sdk:1.23.0' implementation 'dev.openfeature.contrib.providers:flagd:0.11.20' ``` ### Step 3: Add code The `OpenFeatureAPI` class is the main access point to OpenFeature SDK. This class is designed to act as a singleton. Let's define a simple bean configuration class called `OpenFeatureBeans`, that allows us to inject the `OpenFeatureAPI` singleton into desired Spring components. Create the Java class `OpenFeatureBeans.java` inside the package `com.demo` and add the following code, ```java package com.demo; import dev.openfeature.sdk.OpenFeatureAPI; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OpenFeatureBeans { @Bean public OpenFeatureAPI openFeatureAPI() { final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance(); return openFeatureAPI; } } ``` Then we can add the REST endpoint definition of `/hello` endpoint, along with dependency injection for `OpenFeatureAPI` instance. Create the Java class `RestHello.java` inside the package `com.demo` and add the following code. ```java package com.demo; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RestHello { private final OpenFeatureAPI openFeatureAPI; @Autowired public RestHello(OpenFeatureAPI OFApi) { this.openFeatureAPI = OFApi; } @GetMapping("/hello") public String getHello() { final Client client = openFeatureAPI.getClient(); // Evaluate welcome-message feature flag if (client.getBooleanValue("welcome-message", false)) { return "Hello, welcome to this OpenFeature-enabled website!"; } return "Hello!"; } } ``` At this point, we are ready to run the initial version of our application. ### Step 4: Run the initial application Let's compile and run the application. **Maven** ```shell mvn clean install java -jar target/demo-0.0.1-SNAPSHOT.jar ``` **Gradle** ```shell ./gradlew clean build java -jar build/libs/demo-0.0.1-SNAPSHOT.jar ``` Now you can visit the url and observe the message **Hello!**. "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. ### Step 5: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Finally, let's add the required code change to `OpenFeatureBeans.java` class. ```java package com.demo; + import dev.openfeature.contrib.providers.flagd.FlagdProvider; + import dev.openfeature.sdk.exceptions.OpenFeatureError; import dev.openfeature.sdk.OpenFeatureAPI; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OpenFeatureBeans { @Bean public OpenFeatureAPI OpenFeatureAPI() { final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance(); + // Use flagd as the OpenFeature provider and use default configurations + try { + openFeatureAPI.setProviderAndWait(new FlagdProvider()); + } catch (OpenFeatureError e) { + throw new RuntimeException("Failed to set OpenFeature provider", e); + } + return openFeatureAPI; } } ``` ### Step 6: Rerun the application Let's rerun our Java Spring Boot application. **Maven** ```shell mvn clean install java -jar target/demo-0.0.1-SNAPSHOT.jar ``` **Gradle** ```shell ./gradlew clean build java -jar build/libs/demo-0.0.1-SNAPSHOT.jar ``` Revisit the endpoint and you will see the same value. Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Revisit the endpoint and you will be greeted with `Hello, welcome to this OpenFeature-enabled website!` ## Conclusion This walk-through introduced you to the OpenFeature Java SDK and how it can be easily integrated into well-known frameworks such as Spring Boot. It covered how a provider can be configured to perform the flag evaluation and introduced basic feature flagging concepts. It also showcased how feature flags can be updated at runtime, without requiring a code change and a redeployment. --- # Getting Started with the OpenFeature JavaScript SDK on Express ## Introduction This walk-through teaches you the basics of using OpenFeature with JavaScript using Express. You'll learn how to: * Install the JavaScript SDK * Install and configure a provider * Perform basic feature flagging ## Requirements This walk-through assumes that: * You have a basic understanding of JavaScript, Node.js and Express. If not, please review the [Introduction to Node.js](https://nodejs.dev/en/learn/) and the [Express documentation](https://expressjs.com/). * You have Node 16 or later. The latest version of Node can be found [here](https://nodejs.org/en/download/). * You have Docker installed and running on the host system. The latest version of Docker can be found [here](https://docs.docker.com/engine/install/). ## Walk-through ### Step 1: Create a new Node.js project To get started, create a new folder, bootstrap the project, and install the dependencies. This can be done by running the following commands. **JavaScript** ```sh mkdir openfeature-js-intro cd openfeature-js-intro npm init -y npm install express ``` **TypeScript** ```sh mkdir openfeature-ts-intro cd openfeature-ts-intro npm init -y npm install express npm i -D typescript @types/express @types/node ts-node npx tsc --init ``` ### Step 2: Create an Express app **JavaScript** Create a new file named `index.js` and include the following code. ```js const express = require('express'); const app = express(); const port = 8080; app.get('/', (_, res) => { res.send('Express + TypeScript Server'); }); app.listen(port, () => { console.log(`⚡️[server]: Server is running at http://localhost:${port}`); }); ``` **TypeScript** Create a new file named `index.ts` and include the following code. ```ts import express from 'express'; const app = express(); const port = 8080; app.get('/', (_, res) => { res.send('Express + TypeScript Server'); }); app.listen(port, () => { console.log(`⚡️[server]: Server is running at http://localhost:${port}`); }); ``` ### Step 3: Add the OpenFeature SDK Let's install the OpenFeature SDK using the following command. ```sh npm install @openfeature/server-sdk ``` **JavaScript** Update `index.js` to import the SDK. ```js const express = require('express'); + const { OpenFeature } = require('@openfeature/server-sdk'); ``` **TypeScript** Update `index.ts` to import the SDK. ```ts import express from "express"; + import { OpenFeature } from "@openfeature/server-sdk"; ``` Once you've imported `OpenFeature`, a new client can be created. ```js const port = 8080; + const client = OpenFeature.getClient(); ``` The client can now be used to get a feature flag value. In this case, we'll get a `boolean` value using the `welcome-message` [flag key](https://openfeature.dev/specification/glossary.md#flag-key). The second argument is the fallback value, which is returned if there's abnormal behavior. ```js - app.get("/", (_, res) => { + app.get("/", async (_, res) => { + const showWelcomeMessage = await client.getBooleanValue("welcome-message", false); + if (showWelcomeMessage) { + res.send("Express + TypeScript + OpenFeature Server"); + } else { - res.send("Express + TypeScript Server"); + res.send("Express + TypeScript Server"); + } }); ``` ### Step 4: Run the application Let's start the app and see it in action. Run the following command to start the server. **JavaScript** ```sh node index.js ``` **TypeScript** ```sh npx ts-node index.ts ``` Open your favorite browser and navigate to . If all goes as planned, you should see "Express + TypeScript Server" in glorious monochrome. "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. > NOTE: You should stop the app by using the keyboard short `ctrl + c` before moving on to the next step. ### Step 5: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Now let's make the required code changes in our application. Using NPM, install the `flagd provider` with the following command: ```sh npm install @openfeature/flagd-provider ``` **JavaScript** Now, update `index.js` to import the `flagd provider`. ```js const { OpenFeature } = require('@openfeature/server-sdk'); + const { FlagdProvider } = require('@openfeature/flagd-provider'); ``` **TypeScript** Now, update `index.ts` to import the `flagd provider`. ```ts import { OpenFeature } from "@openfeature/server-sdk"; + import { FlagdProvider } from "@openfeature/flagd-provider"; ``` Finally, we need to register the provider with OpenFeature. ```js const port = 8080; + OpenFeature.setProvider(new FlagdProvider()); const client = OpenFeature.getClient(); ``` ### Step 6: Rerun the application Now that everything is in place, let's start the app again. **JavaScript** ```sh node index.js ``` **TypeScript** ```sh npx ts-node index.ts ``` Open your favorite browser and navigate to should show the same value as before. This difference is now the feature flag value can be changed at runtime! Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Save the changes to `flag.json` and refresh the browser tab. You should now be greeted with `Express + TypeScript + OpenFeature Server`. ## Conclusion This walk-through introduced you to the OpenFeature JS SDK. It covered how a provider can be configured to perform the flag evaluation and introduced basic feature flagging concepts. It also showcased how feature flags can be updated at runtime, without requiring a redeployment. --- # Getting Started with the OpenFeature PHP SDK on Laravel ## Introduction This walk-through teaches you the basics of using OpenFeature with PHP using Laravel. You'll learn how to: * Install the OpenFeature PHP SDK * Install and configure an OpenFeature provider * Perform basic feature flagging ## Requirements This walk-through assumes that: * You should have a good understanding of PHP syntax, data types, operators, functions, and control structures. * You have PHP 8.0 or later. * You have Docker installed and running on the host system. ## Walk-through ### Step 1: Create a new Laravel PHP project To get started, create a new folder, bootstrap the project, and install the dependencies. This can be done by running the following commands. ```sh composer create-project --prefer-dist laravel/laravel openfeature-php-intro cd openfeature-php-intro ``` ### Step 2: Create a sample response Define a route for your API by adding the following code to the `routes/api.php` file. ```php Route::get('/hello', function () { return response()->json(['message' => 'Hello, World!']); }); ``` ### Step 3: Add the OpenFeature SDK Let's install the OpenFeature SDK using the following command. ```sh composer require open-feature/sdk ``` Update `routes/api.php` to import the SDK ```php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; + use OpenFeature\OpenFeatureAPI; ``` The client can now be used to get a feature flag value. In this case, we'll get a boolean value using the welcome-message flag key. The second argument is the fallback value, which is returned if there's abnormal behavior. ```php Route::get('/hello', function () { + $api = OpenFeatureAPI::getInstance(); + $client = $api->getClient(); + if($client->getBooleanValue("welcome-message", false)){ + return response()->json(['message' => 'Hello, World! open feature']); + } return response()->json(['message' => 'Hello, World!']); }); ``` ### Step 4: Run the application Let's start the app and see it in action. Run the following command to start the server. ```sh php artisan serve ``` Open your favorite browser and navigate to . If all goes as planned, you should see the message "Hello, World!" in glorious monochrome. "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. > NOTE: You should stop the app by using the keyboard short `ctrl + c` before moving on to the next step. ### Step 5: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Also, we have to install PHP flagd provider. Open up your terminal and write this command. ```sh composer require open-feature/flagd-provider ``` Now let's make the required code changes in our application. Now, update `routes/api.php` to import the `flagd provider`. ```php use OpenFeature\OpenFeatureAPI; + use OpenFeature\Providers\Flagd\FlagdProvider; + use OpenFeature\Providers\Flagd\config\HttpConfig; + use GuzzleHttp\Client; + use GuzzleHttp\Psr7\HttpFactory; ``` Finally, we need to register the provider with OpenFeature. ```php Route::get('/hello', function () { $api = OpenFeatureAPI::getInstance(); + + $httpClient = new Client(); + $httpFactory = new HttpFactory(); + $api->setProvider(new FlagdProvider([ + 'httpConfig' => new HttpConfig( + $httpClient, + $httpFactory, + $httpFactory, + ) + ])); $client = $api->getClient(); if($client->getBooleanValue("welcome-message", false)){ return response()->json(['message' => 'Hello, World! open feature']); } return response()->json(['message' => 'Hello, World!']); }); ``` ### Step 6: Rerun the application Now that everything is in place, let's start the app again. ```sh php artisan serve ``` Open your favorite browser and navigate to should show the same value as before. This difference is now the feature flag value can be changed at runtime! Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Save the changes to `flags.flagd.json` and refresh the browser tab. ## Conclusion This walk-through introduced you to the OpenFeature PHP SDK. It covered how a provider can be configured to perform the flag evaluation and introduced basic feature flagging concepts. It also showcased how feature flags can be updated at runtime, without requiring a redeployment. --- # Getting Started with the OpenFeature Python SDK ## Introduction This walk-through teaches you the basics of using OpenFeature with Python. You'll learn how to: * Install the Python SDK * Install and configure a provider * Perform basic feature flagging ## Requirements This walk-through assumes that: * You have a basic understanding of Python and Flask. * You have Flask 3.x and Python 3.10 or later. * You have Docker installed and running on the host system. The latest version of Docker can be found [here](https://docs.docker.com/engine/install/). > **NOTE:** If you don't have docker installed, check the available options to install Flagd [here](https://flagd.dev/installation/). ## Walk-through ### Step 1: Create a minimal flask application To get started, create a new folder, bootstrap the project, and install the dependencies. This can be done by running the following commands. ```sh mkdir openfeature-python-intro cd openfeature-python-intro pip install Flask ``` ### Step 2: Create a Flask app Create a new file named `app.py` inside openfeature-python-intro directory and include the following code. ```python from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Flask Server" ``` ### Step 3: Add the OpenFeature SDK Let's install the OpenFeature SDK using the following commands. ```sh pip install typing-extensions pip install openfeature-sdk pip install openfeature-provider-flagd ``` Update `app.py` to import the SDK. ```python from openfeature import api from openfeature.contrib.provider.flagd import FlagdProvider ``` Once you've imported `OpenFeature`, a new client can be created using the `FlagdProvider`. ```python api.set_provider(FlagdProvider()) client = api.get_client() ``` The client can now be used to get a feature flag value. In this case, we'll get a `boolean` value using the `welcome-message` [flag key](https://openfeature.dev/specification/glossary.md#flag-key) and fallback value, which is returned if there's abnormal behavior using the `client.get_boolean_value()` method. ```python @app.route("/") def index(): show_welcome_message = client.get_boolean_value("welcome-message", False) if show_welcome_message: return "Flask + OpenFeature Server" return "Flask Server" ``` ### Step 4: Run the application Let's start the app and see it in action, use the final code below. ```python from flask import Flask from openfeature import api from openfeature.contrib.provider.flagd import FlagdProvider app = Flask(__name__) api.set_provider(FlagdProvider()) client = api.get_client() @app.route("/") def index(): show_welcome_message = client.get_boolean_value("welcome-message", False) if show_welcome_message: return "Flask + OpenFeature Server" return "Flask Server" ``` Run the following command to start the server. ```sh flask run ``` Open your favorite browser and navigate to . If all goes as planned, you should see "Flask Server". "Why I'm I seeing that value?", you may ask. Well, it's because a provider hasn't been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you'll learn how to add a provider. > NOTE: You should stop the app by using the keyboard short `ctrl + c` before moving on to the next step. ### Step 5: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Flagd can be run as a [standalone binary](https://flagd.dev/reference/flagd-cli/flagd/) or [Kubernetes Operator](https://openfeature.dev/docs/tutorials/ofo/) as well. If you don't have docker installed, get and install the [Flagd binary](https://github.com/open-feature/flagd/releases). With the flagd configuration in place, start flagd service with the following command. ```sh flagd start -f file:flags.flagd.json ``` ### Step 6: Rerun the application Now that everything is in place, let's start the app again. ```sh flask run ``` Open your browser and navigate to should show the same value as before. This difference is now the feature flag value can be changed at runtime! Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Save the changes to `flags.flagd.json` and refresh the browser tab. You should now be greeted with `Flask + OpenFeature Server`. ## Conclusion This walk-through introduced you to the OpenFeature Python SDK. It covered how a provider can be configured to perform the flag evaluation and introduced basic feature flagging concepts. It also showcased how feature flags can be updated at runtime, without requiring a redeployment. --- # Getting Started with the OpenFeature Rust SDK and Axum ## Introduction This tutorial explains how to integrate the OpenFeature Rust SDK into an Axum application using the flagd provider. In this guide you will learn how to: * Create a new Rust project with Cargo * Update dependencies to include Docker-based flagd configuration * Set up an Axum server with a custom AppState that holds a FlagdProvider * Evaluate a feature flag and return a custom message with diff markers to highlight changes ## Requirements * Rust stable (1.80.2 or later recommended) * Cargo * Docker (to run the flagd service) * A valid flagd configuration file (e.g. `flags.flagd.json`) ## Walk-through ### Step 1: Create a new Rust project ```bash cargo new axumstart ``` Then navigate to the project directory: ```bash cd axumstart ``` ### Step 2: Update Cargo.toml dependencies In your Cargo.toml add the following dependencies: ```toml [dependencies] axum = "0.6" tokio = { version = "1", features = ["full"] } serde_json = "1.0" tracing = "0.1" open-feature = "^0" open-feature-flagd = "^0" ``` Adjust version numbers according to the latest releases. ### Step 3: Set Up the Application Create or modify your `src/main.rs` with the following code. Notice the diff markers to indicate new additions: ```rust - fn main() { - println!("Hello, world!"); - } + use axum::{ + extract::State, + http::StatusCode, + response::IntoResponse, + routing::get, + Json, Router, + }; + use open_feature_flagd::{FlagdOptions, FlagdProvider}; + use open_feature::{EvaluationContext, provider::FeatureProvider}; + use serde_json::json; + use std::sync::Arc; + + #[derive(Clone)] + pub struct AppState { + pub flag_provider: Arc, + } + + async fn hello_handler(State(state): State) -> impl IntoResponse { + // Create an evaluation context (custom fields can be added if required) + let context = EvaluationContext::default(); + + // Evaluate the "welcome-message" feature flag. + let flag_eval = state.flag_provider.resolve_bool_value("welcome-message", &context).await; + let message = match flag_eval { + Ok(result) if result.value => "Hello, welcome to this OpenFeature-enabled website!", + _ => "Hello!", + }; + + (StatusCode::OK, Json(json!({ "message": message }))) + } + + #[tokio::main] + async fn main() { + // Initialize the flagd provider with default options. + let flag_provider = Arc::new( + FlagdProvider::new(FlagdOptions { + cache_settings: None, + ..Default::default() + }) + .await + .expect("Failed to connect to flagd. Please confirm flagd is running."), + ); + + // Create a custom application state with the flag provider. + let app_state = AppState { flag_provider }; + + // Build an Axum application with the "/hello" endpoint. + let app = Router::new() + .route("/hello", get(hello_handler)) + .with_state(app_state); + + let addr = "127.0.0.1:8080".parse().unwrap(); + println!("Listening on http://{}/hello", addr); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); + } ``` ### Step 4: Configure a provider (flagd) Providers are an important concept in OpenFeature because they are responsible for the flag evaluation itself. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we'll need to register a provider. Create a new file named `flags.flagd.json` and add the following JSON. Notice that there's a flag called `welcome-message` which matches the flag key referenced earlier. The `welcome-message` flag has `on` and `off` variants that return `true` and `false` respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is `off`, therefore the value `false` would be returned. ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", "defaultVariant": "off" } } } ``` > NOTE: This configuration is specific for flagd and varies across providers. With the flagd configuration in place, start flagd service with the following docker command. > NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker () ```sh docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json ``` Flagd can be run as a [standalone binary](https://flagd.dev/reference/flagd-cli/flagd/) or [Kubernetes Operator](https://openfeature.dev/docs/tutorials/ofo/) as well. If you don't have docker installed, get and install the [Flagd binary](https://github.com/open-feature/flagd/releases). With the flagd configuration in place, start flagd service with the following command. ```sh flagd start -f file:flags.flagd.json ``` ### Step 6: Rerun the application Now that everything is in place, let's start the app. ```sh cargo run ``` Open your browser and navigate to should show the same value as before. This difference is now the feature flag value can be changed at runtime! Let's change the feature flag in our `flags.flagd.json`, making `defaultVariant` to `on` ```json { "flags": { "welcome-message": { "variants": { "on": true, "off": false }, "state": "ENABLED", - "defaultVariant": "off" + "defaultVariant": "on" } } } ``` Save the changes to `flags.flagd.json` and refresh the browser tab. You should now be greeted with `Hello, welcome to this OpenFeature-enabled website!`. ## Conclusion This tutorial demonstrated how to integrate the OpenFeature Rust SDK with an Axum web server using a custom AppState containing a FlagdProvider. By leveraging flagd service, you can dynamically update feature flags at runtime without needing to redeploy your application. For advanced configuration and more details, refer to the [OpenFeature Rust SDK documentation](https://docs.rs/open-feature-flagd/latest/open_feature_flagd/). --- # OpenFeature CLI > **⚠️ The OpenFeature CLI is experimental!** Feel free to give it a shot and provide feedback, but expect breaking changes. ## Overview The OpenFeature CLI is a command-line tool designed to improve the developer experience when working with feature flags. It helps developers manage feature flags consistently across different environments and programming languages by providing powerful utilities for code generation, flag validation, and more. ## Setup Before we jump in, let's get everything ready. You'll need to install the OpenFeature CLI. Choose the method that works best for you: ### curl The OpenFeature CLI can be installed using a shell command. This method is suitable for most Unix-like operating systems. ```bash curl -fsSL https://openfeature.dev/scripts/install_cli.sh | sh ``` ### docker The OpenFeature CLI is available as a Docker image in the [GitHub Container Registry](https://github.com/open-feature/cli/pkgs/container/cli). You can run the CLI in a Docker container using the following command: ```bash docker run -it -v $(pwd):/local -w /local ghcr.io/open-feature/cli:latest ``` ### go If you have `Go >= 1.23` installed, you can install the CLI using the following command: ```bash go install github.com/open-feature/cli/cmd/openfeature@latest ``` ### pre-built binaries Download the appropriate pre-built binary from the [releases page](https://github.com/open-feature/cli/releases). ## Your first flag manifest Every great feature flagging setup starts with knowing what flags you have. Let's create a simple flag manifest that defines a few common flags you might use in a real project: ```bash cat > flags.json << EOF { "$schema": "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag-manifest.json", "flags": { "newUserOnboarding": { "description": "Shows the new streamlined onboarding flow", "flagType": "boolean", "defaultValue": false }, "maxLoginAttempts": { "description": "Maximum number of login attempts before lockout", "flagType": "integer", "defaultValue": 3 }, "welcomeMessage": { "description": "Personalized welcome message for users", "flagType": "string", "defaultValue": "Welcome!" } } } EOF ``` This manifest defines three flags `newUserOnboarding`, `maxLoginAttempts` and `welcomeMessage` that you might find in any web application. Notice how each flag has a clear `description`, a `type`, and a sensible `default value`. ## Flag Manifest The flag manifest is a JSON file that defines your feature flags and their properties. It serves as the source of truth for your feature flags and is used by the CLI to generate strongly typed accessors. The manifest file should be named `flags.json` and placed in the root of your project. ### Flag Manifest Structure The flag manifest file should follow the JSON schema defined [here](https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag-manifest.json). The schema defines the following properties: * `$schema`: The URL of the JSON schema for validation. * `flags`: An object containing the feature flags. * `flagKey`: A unique key for the flag. * `description`: A description of what the flag does. * `flagType`: The type of the flag (e.g., `boolean`, `string`, `number`, `object`). * `defaultValue`: The default value of the flag. ## Code generation Let's generate strongly typed flag accessors. The OpenFeature CLI supports multiple languages, so let's see what's available through the `generate` command. ```bash ❯ openfeature generate ``` This will display a list of supported languages and their respective description: ```bash Available generators: Generator | Description | Stability csharp | Generate typesafe C# client. | alpha go | Generate typesafe accessors for OpenFeature. | alpha java | Generate typesafe Java client. | alpha nestjs | Generate typesafe NestJS decorators. | alpha nodejs | Generate typesafe Node.js client. | alpha python | Generate typesafe Python client. | alpha react | Generate typesafe React Hooks. | alpha ``` Let's start with NodeJs decorators since it's widely used: ```bash ❯ openfeature generate nodejs ``` After running this command, you should see output similar to the following: ```bash INFO Generating a typesafe client for Node.js SUCCESS Created openfeature.ts SUCCESS Successfully generated client. Happy coding! ``` This will create a `openfeature.ts` file with strongly typed accessors for the flags defined in your manifest. No more string literals, no more guessing what type a flag returns. The file will look something like this: ```javascript // AUTOMATICALLY GENERATED BY OPENFEATURE CLI, DO NOT EDIT. import { OpenFeature, stringOrUndefined, objectOrUndefined, } from "@openfeature/server-sdk"; import type { EvaluationContext, EvaluationDetails, FlagEvaluationOptions, } from "@openfeature/server-sdk"; export interface GeneratedClient { /** * Maximum number of login attempts before lockout * * **Details:** * - flag key: `maxLoginAttempts` * - default value: `3` * - type: `number` * * Performs a flag evaluation that returns a number. * @param {EvaluationContext} context The evaluation context used on an individual flag evaluation * @param {FlagEvaluationOptions} options Additional flag evaluation options * @returns {Promise} Flag evaluation response */ maxLoginAttempts(context?: EvaluationContext, options?: FlagEvaluationOptions): Promise; /** * Maximum number of login attempts before lockout * * **Details:** * - flag key: `maxLoginAttempts` * - default value: `3` * - type: `number` * * Performs a flag evaluation that a returns an evaluation details object. * @param {EvaluationContext} context The evaluation context used on an individual flag evaluation * @param {FlagEvaluationOptions} options Additional flag evaluation options * @returns {Promise>} Flag evaluation details response */ maxLoginAttemptsDetails(context?: EvaluationContext, options?: FlagEvaluationOptions): Promise>; /** * Shows the new streamlined onboarding flow * * **Details:** * - flag key: `newUserOnboarding` * - default value: `false` * - type: `boolean` * * Performs a flag evaluation that returns a boolean. * @param {EvaluationContext} context The evaluation context used on an individual flag evaluation * @param {FlagEvaluationOptions} options Additional flag evaluation options * @returns {Promise} Flag evaluation response */ newUserOnboarding(context?: EvaluationContext, options?: FlagEvaluationOptions): Promise; /** * Shows the new streamlined onboarding flow * * **Details:** * - flag key: `newUserOnboarding` * - default value: `false` * - type: `boolean` * * Performs a flag evaluation that a returns an evaluation details object. * @param {EvaluationContext} context The evaluation context used on an individual flag evaluation * @param {FlagEvaluationOptions} options Additional flag evaluation options * @returns {Promise>} Flag evaluation details response */ newUserOnboardingDetails(context?: EvaluationContext, options?: FlagEvaluationOptions): Promise>; /** * Personalized welcome message for users * * **Details:** * - flag key: `welcomeMessage` * - default value: `Welcome!` * - type: `string` * * Performs a flag evaluation that returns a string. * @param {EvaluationContext} context The evaluation context used on an individual flag evaluation * @param {FlagEvaluationOptions} options Additional flag evaluation options * @returns {Promise} Flag evaluation response */ welcomeMessage(context?: EvaluationContext, options?: FlagEvaluationOptions): Promise; /** * Personalized welcome message for users * * **Details:** * - flag key: `welcomeMessage` * - default value: `Welcome!` * - type: `string` * * Performs a flag evaluation that a returns an evaluation details object. * @param {EvaluationContext} context The evaluation context used on an individual flag evaluation * @param {FlagEvaluationOptions} options Additional flag evaluation options * @returns {Promise>} Flag evaluation details response */ welcomeMessageDetails(context?: EvaluationContext, options?: FlagEvaluationOptions): Promise>; } /** * A factory function that returns a generated client that not bound to a domain. * It was generated using the OpenFeature CLI and is compatible with `@openfeature/server-sdk`. * * All domainless or unbound clients use the default provider set via {@link OpenFeature.setProvider}. * @param {EvaluationContext} context Evaluation context that should be set on the client to used during flag evaluations * @returns {GeneratedClient} Generated OpenFeature Client */ export function getGeneratedClient(context?: EvaluationContext): GeneratedClient /** * A factory function that returns a domain-bound generated client that was * created using the OpenFeature CLI and is compatible with the `@openfeature/server-sdk`. * * If there is already a provider bound to this domain via {@link OpenFeature.setProvider}, this provider will be used. * Otherwise, the default provider is used until a provider is assigned to that domain. * @param {string} domain An identifier which logically binds clients with providers * @param {EvaluationContext} context Evaluation context that should be set on the client to used during flag evaluations * @returns {GeneratedClient} Generated OpenFeature Client */ export function getGeneratedClient(domain: string, context?: EvaluationContext): GeneratedClient export function getGeneratedClient(domainOrContext?: string | EvaluationContext, contextOrUndefined?: EvaluationContext): GeneratedClient { const domain = stringOrUndefined(domainOrContext); const context = objectOrUndefined(domainOrContext) ?? objectOrUndefined(contextOrUndefined); const client = domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context) return { maxLoginAttempts: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise => { return client.getNumberValue("maxLoginAttempts", 3, context, options); }, maxLoginAttemptsDetails: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise> => { return client.getNumberDetails("maxLoginAttempts", 3, context, options); }, newUserOnboarding: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise => { return client.getBooleanValue("newUserOnboarding", false, context, options); }, newUserOnboardingDetails: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise> => { return client.getBooleanDetails("newUserOnboarding", false, context, options); }, welcomeMessage: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise => { return client.getStringValue("welcomeMessage", "Welcome!", context, options); }, welcomeMessageDetails: (context?: EvaluationContext, options?: FlagEvaluationOptions): Promise> => { return client.getStringDetails("welcomeMessage", "Welcome!", context, options); }, } } ``` You can now use the generated client in your Node.js application, and it will provide type-safe access to your feature flags. ## Using the generated client Now that we have our generated client, we can use it in our Node.js application. Let's create a simple server that uses the generated client to evaluate flags: ```javascript import { getGeneratedClient } from './openfeature'; // Create your strongly typed client const flags = getGeneratedClient(); // You get this type-safe approach: const isEnabled = await flags.newUserOnboarding(); const attempts = await flags.maxLoginAttempts(); const message = await flags.welcomeMessage(); ... const details = await flags.newUserOnboardingDetails(); // Returns Promise> ``` ## Configuration The OpenFeature CLI uses an optional configuration file to override default settings and customize the behavior of the CLI. This file can be in JSON or YAML format and should be named either `.openfeature.json` or `.openfeature.yaml`. ### Configuration File Structure ```yaml # Example .openfeature.yaml manifest: "flags/manifest.json" # Overrides the default manifest path generate: output: "src/flags" # Overrides the default output directory # Any language-specific options can be specified here # For example, for React: react: output: "src/flags/react" # Overrides the default React output directory # For Go: go: package: "github.com/myorg/myrepo/flags" # Overrides the default Go package name output: "src/flags/go" # Overrides the default Go output directory ``` ## Next Steps Want to dive deeper? Here are some great next steps: * Explore more generators: Try `openfeature generate` to see all supported languages * Set up provider integration: Connect your generated code to a real feature flag service using [OpenFeature providers](https://openfeature.dev/ecosystem.md?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider) * Contribute: Visit the [GitHub repository](https://github.com/open-feature/cli) to contribute or report issues * Join the community: Drop by the [#openfeature-cli](https://cloud-native.slack.com/archives/C07DY4TUDK6) channel in CNCF Slack --- # In-Process and Client-side Evaluation with flagd and OpenFeature Operator This tutorial outlines some of the advanced features and configurations supported by the OpenFeature operator and flagd. The example configurations featured here make use of flagd used in a "standalone" mode; deployed and accessible centrally in your cluster (as opposed to injected into the workload as a sidecar). The model adds some complexity in the configuration, but carries a few benefits: * Centralized flagd deployments work well in conjunction with [in-process evaluation](https://flagd.dev/architecture/#in-process-evaluation), which speeds up feature-flag evaluation by performing flag evaluation directly in your application. * Centralized flagd deployments are ideal for supporting [client-side](https://openfeature.dev/specification/glossary#client-side-sdk) evaluation (allowing web or mobile client applications to evaluate feature flags). Enough talk, let's go! With that intro out of the way, let's get our hands dirty! ## Prerequisites * We recommend you understand the [OFO quick start tutorial](https://openfeature.dev/docs/tutorials/open-feature-operator/quick-start.md) first. * We'll also need the following tools: * [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation): a tool for running local Kubernetes clusters using Docker container “nodes” * [kubectl](https://kubernetes.io/docs/tasks/tools/) * [k9s](https://k9scli.io/) (optional, if you'd like to inspect your cluster visually) * [helm](https://helm.sh/) (optional, if you prefer to install the operator using the Helm chart) Important If you are using Kubernetes on Rancher Desktop, *you must temporarily disable `Traefik`* for this demo. See below for details and instructions. ### Rancher Desktop configuration Disable Traefik in Rancher Desktop Rancher Desktop uses K3s under the hood, which in turn uses Traefik as the default ingress controller for your Kubernetes cluster. We need to use NGINX for this demo, which requires us to disable Traefik. **Uncheck `Enable Traefik`** from the Kubernetes Settings page. You may need to exit and restart Rancher Desktop for the change to take effect. ![k9s](/assets/images/rancher-traefik-f7c24dd615113b03968333d55553e35e.png) See [Rancher docs](https://docs.rancherdesktop.io/how-to-guides/setup-NGINX-Ingress-Controller/) for more information. ## Show me the commands ### Building our cluster Cluster Setup and Configuration OK, let's get our cluster up and running! We strongly recommend using `kind` for this demo, but if you already have a K8s cluster, you can skip to [Install cert-manager](#install-cert-manager). Note If not using kind, you will have to handle forwarding ports and exposing ingresses as appropriate for your distribution or infrastructure. ### Using Kind Download the cluster definition file, `kind-cluster-advanced-topics.yaml`: ```shell curl -sfL https://raw.githubusercontent.com/open-feature/openfeature.dev/main/static/samples/kind-cluster-advanced-topics.yaml > kind-cluster-advanced-topics.yaml ``` Then, create our cluster using the `kind-cluster-advanced-topics.yaml` file: ```shell kind create cluster --config kind-cluster-advanced-topics.yaml ``` This might take a minute or two. While the cluster is starting up, we can take a moment to look at the cluster definition. Open `kind-cluster-advanced-topics.yaml` and take note that we expose port 80, and also supply a `kubeadmConfigPatch` which enables ingress usage with `kind`. ### Install cert-manager Next, because our operator makes use of webhooks, we need some certificate infrastructure in our cluster. If your cluster already has cert manager, or you're using another solution for certificate management, you can go to [Install OpenFeature operator](#install-openfeature-operator). Install cert-manager, and wait for it to be ready: ```shell kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml && \ kubectl wait --timeout=60s --for condition=Available=True deploy --all -n 'cert-manager' ``` ## Install NGINX controller For our NGINX ingress to work with `kind`, we need to install some additional components: Download our NGINX controller configuration customization: ```shell curl -sfL https://raw.githubusercontent.com/open-feature/openfeature.dev/main/static/samples/nginx-config.yaml > nginx-config.yaml curl -sfL https://raw.githubusercontent.com/open-feature/openfeature.dev/main/static/samples/nginx-kind-patch.yaml > nginx-kind-patch.yaml ``` Install the NGINX controller: ```shell kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.15.1/deploy/static/provider/kind/deploy.yaml kubectl apply -f nginx-config.yaml kubectl patch deployment ingress-nginx-controller -n ingress-nginx --patch-file nginx-kind-patch.yaml kubectl wait --timeout=60s --for condition=Available=True deploy --all -n 'ingress-nginx' ``` This manifest contains kind-specific patches to forward the hostPorts to the ingress controller, set taint tolerations and schedule it to the custom labelled node. ### Install OpenFeature operator And finally, let's install the operator itself: Install using manifest ```shell kubectl apply -f https://github.com/open-feature/open-feature-operator/releases/download/v0.9.0/release.yaml && \ kubectl wait --timeout=60s --for condition=Available=True deploy --all -n 'open-feature-operator-system' ``` Install using Helm ```shell helm repo add openfeature https://open-feature.github.io/open-feature-operator/ && \ helm repo update && \ helm upgrade --install open-feature-operator openfeature/open-feature-operator ``` Note When using Helm, various configuration parameters can be set, such as resource limits and default configuration values. See the full [chart documentation](https://artifacthub.io/packages/helm/open-feature-operator/open-feature-operator#configuration) for details. ## In-process Evaluation First, lets see how a centralized flagd can be used to serve flags to an *in-process* workload. ### Downloading and understanding the `in-process-evaluation.yaml` manifest Download the file defining our demo deployment, service and custom resource (CRs), `in-process-evaluation.yaml`: ```shell curl -sfL https://raw.githubusercontent.com/open-feature/playground/main/config/k8s/in-process-evaluation.yaml > in-process-evaluation.yaml ``` Let's take a look at the manifest and try to understand what's happening... The `in-process-evaluation.yaml` manifest contains a few resources: * A feature flag definition (`kind: FeatureFlag`), and a flag source (`kind: FeatureFlagSource`) (these should be familiar to you from the [quick start](https://openfeature.dev/docs/tutorials/open-feature-operator/quick-start.md)). * A flagd definition (`kind: Flagd`) which defines a "standalone" flagd. * This flagd is available centrally and fronted by a `Service`, so that any workload can connect to it. * This can save resources compared to injected sidecars into every relevant workload. * An in-process flagd provider configuration (`kind: InProcessConfiguration`) which configures our workload to evaluate feature flags in-process, after retrieving definitions from the centralized flagd instance. * Evaluating flags in process involves no network I/O or serialization, improving latency and overall performance. * Our `Deployment`, with an annotation associating it with the in-process provider configuration (`openfeature.dev/inprocessconfiguration: 'in-process-config'`); this configures the flagd provider in the workload to use the in-process configuration. ### Apply the `in-process-evaluation.yaml` manifest ```shell kubectl -n default apply -f in-process-evaluation.yaml && \ kubectl wait --timeout=60s deployment --for condition=Available=True -l 'app=open-feature-demo' -n 'default' ``` If you're using `k9s` or some other means of visualization, the pods your cluster should look something like this: ![k9s](/assets/images/k9s-in-process-6cb5254fb790d2edd252ffe939e3ebfc.png) Take note of the `flagd-in-process` pod. ### Test the in-process configuration Let's make sure everything is working as expected by changing the value of a backend flag. Open your browser and navigate to [localhost](http://localhost). ![ui-in-process-preview](/assets/images/ui-in-process-69bdc9e2693d48d04ce3a1d974e7d8c1.png) Important If you see a 404 and you're using Rancher Desktop, you may have forgotten to [disable Traefik](#rancher-desktop-configuration). Click the fibonacci calculator; it should take \~4000ms to return a value using the `"recursive"` algorithm. Now, change the `defaultValue` of the `"fib-algo"` flag in our `in-process-evaluation.yaml` manifest from `"recursive"` to `"binet"`: ```yaml fib-algo: variants: recursive: recursive memo: memo loop: loop binet: binet - defaultVariant: recursive + defaultVariant: binet ``` Apply the manifest again: ```shell kubectl -n default apply -f in-process-evaluation.yaml && \ kubectl wait --timeout=60s deployment --for condition=Available=True -l 'app=open-feature-demo' -n 'default' ``` Now, if you run the fibonacci calculator again, it should take \~5ms to return a value. We see our flag system is working! ### How does the in-process configuration work? ```mermaid block-beta columns 2 block:cluster columns 3 block:resources columns 1 resourcesLabel["resources"] FlagSource>"FlagSource"] space Flags>"FeatureFlag"] end space block:workload columns 1 flagd["flagd in-process provider"] workloadLabel["workload"] end end flagd -- "sync.proto" --> FlagSource FlagSource --> Flags style resourcesLabel fill:none,stroke:none style workloadLabel fill:none,stroke:none ``` The `flagd-in-process` `Flagd` custom resource instance we defined starts up a flagd instance. This instance is configured to use the `app-flags` flag definition through it's association with the `app-flag-source` `FeatureFlagSource`. The operator automatically configures the `flagd-provider` in our workload to connect to the `flagd-in-process` flagd instance via inject environment variables. The provider then uses the [sync.proto](https://buf.build/open-feature/flagd/file/main:flagd/sync/v1/sync.proto) to retrieve the flag definitions (as well as any subsequent updates) from flagd. ## Client-side evaluation Now, lets see how a centralized flagd can be used to support client-side evaluation of feature flags in single-page application. Important It's expected you've already completed the [Building our cluster](#building-our-cluster) section above. If you haven't, you can do that now before continuing. ### Downloading and understanding the `client-side-evaluation.yaml` manifest Download the file defining our demo deployment, service and custom resource (CRs), `in-process-evaluation.yaml`: ```shell curl -sfL https://raw.githubusercontent.com/open-feature/playground/main/config/k8s/client-side-evaluation.yaml > client-side-evaluation.yaml ``` Let's take a look at the manifest... Similar to the in-process evaluation above, the `client-side-evaluation.yaml` manifest contains a few resources: * A feature flag definition (`kind: FeatureFlag`), and a flag source (`kind: FeatureFlagSource`) (these should be familiar to you from the [quick start](https://openfeature.dev/docs/tutorials/open-feature-operator/quick-start.md)). * A flagd definition (`kind: Flagd`) which defines a "standalone" flagd. * The flagd contains an `ingress` spec, which will be used to automatically generate an ingress and associated service to export our flags to the client application. * Note that the ingress spec contains reverse-proxy rules which will forward feature-flag requests to our flagd * Our `Deployment`, which serves a React app using the `@openfeature/react-sdk`. ### Apply the `client-side-evaluation.yaml` manifest ```shell kubectl -n default apply -f client-side-evaluation.yaml && \ kubectl wait --timeout=60s deployment --for condition=Available=True -l 'app=open-feature-demo' -n 'default' ``` If you're using `k9s` or some other means of visualization, the pods your cluster should look something like this: ![k9s](/assets/images/k9s-client-side-d7db7629474d9beb734e7beac320dd0d.png) Take note of the `flagd-ui` pod. ### Test the client-side configuration Let's make sure everything is working as expected. Open your browser and navigate to [localhost](http://localhost) (or refresh, if you're already there). ![ui-client-side-preview](/assets/images/ui-client-side-b08201249d61b3ea65a154085cabf7d8.png) Important If you see a 404 and you're using Rancher Desktop, you may have forgotten to [disable Traefik](#rancher-desktop-configuration). You should see the UI has a blue theme. Now, change the `defaultValue` of the `"hex-color"` flag in our `client-side-evaluation.yaml` manifest from `"blue"` to `"red"`: ```yaml hex-color: variants: red: c05543 green: 2f5230 blue: 0d507b yellow: d4ac0d - defaultVariant: blue + defaultVariant: red state: ENABLED targeting: if: - in: - '@faas.com' - var: - email - yellow - null ``` Apply the manifest again: ```shell kubectl -n default apply -f client-side-evaluation.yaml && \ kubectl wait --timeout=60s deployment --for condition=Available=True -l 'app=open-feature-demo' -n 'default' ``` You should see the UI immediately change color! ### How does the client-side configuration work? The `flagd-ui` `Flagd` custom resource instance we defined starts up a flagd instance. This instance is configured to use the `ui-flags` flag definition through it's association with the `ui-flag-source` `FeatureFlagSource`. The operator automatically configures an ingress and service which expose this `flagd` instance so it can be accessed outside your cluster. The client app uses the [@openfeature/react-sdk](https://www.npmjs.com/package/@openfeature/react-sdk) to make use of either the [OFREP](https://openfeature.dev/specification/appendix-c/) or `flagd-web` providers to perform flag evaluations (you can switch this with the dropdown in the bottom right). Both the providers support the emission of events when a flag configuration is changed (the `flagd-web` provider maintains a connection with flagd via the connect protocol, while the OFREP provider does HTTP polling). When the providers emit this change event, the `@openfeature/react-sdk` causes the UI to re-render. It's worth noting that these providers do not expose flag evaluation rules or flag configuration in general to the client; the client simply makes a "bulk evaluation" request and retrieves a set of evaluated flags. ```mermaid block-beta columns 3 block:cluster block:resources columns 1 resourcesLabel["resources"] FlagSource>"FlagSource"] space Flags>"FeatureFlag"] end flagd ingress["ingress (NGINX)"] end space block:client columns 2 provider clientLabel["client"] end flagd --> FlagSource FlagSource --> Flags provider -- "OFREP / evaluation.proto" --> ingress style clientLabel fill:none,stroke:none style resourcesLabel fill:none,stroke:none ``` ## Cleaning up If you used a kind cluster, you can clean everything up by running: ```shell kind delete cluster ``` If you [disabled Traefik](#rancher-desktop-configuration), you may want to re-enable it. --- # Quick Start: Cloud Native Feature-Flagging with the OpenFeature Operator In the following tutorial, we'll see how to leverage *flagd* and the OpenFeature Operator to enable cloud-native, self-hosted feature flags in your Kubernetes cluster. [flagd](https://flagd.dev/) is a "feature flag daemon with a Unix philosophy". Put another way, it's a small, self-contained binary that evaluates feature flags, uses standard interfaces, and runs just about anywhere. It can be deployed in a central location serving multiple clients or embedded into a unit of deployment (such as a pod in Kubernetes). The [OpenFeature Operator](https://github.com/open-feature/open-feature-operator) is a K8s-flavored solution for easily adding flagd to any relevant workloads. The operator parses Kubernetes spec files and adds flagd and associated objects to the workloads based on annotations and custom resource definitions it understands. The injected flagd sidecar then gets its feature flags by querying the Kubernetes API for our flags. Note This deployment pattern (injecting flagd as a sidecar which communicates with the kubernetes API directly) is the simplest to configure, but it may not be the right solution for all use-cases. See the [advanced tutorial](https://openfeature.dev/docs/tutorials/open-feature-operator/advanced-topics.md) for alternative models. Let's do it ## Prerequisites * If you don't have access to an existing K8s cluster, you have a few options: * [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) is similar to minikube (another solution for running a cluster locally you may be familiar with) but supports more than one node, so it makes for a slightly more realistic experience. If using kind, this tutorial provides a 3-node cluster definition with a forwarded containerPort for you (more on that later). * [MicroK8s](https://microk8s.io/) and [K3s](https://k3s.io/) are easily installable Kubernetes clusters you can use locally. The benefit of these is that they are basically identical to a production environment. Configuration of `MicroK8s` and `K3s` is out of the scope of this tutorial. * [kubectl](https://kubernetes.io/docs/tasks/tools/) * [k9s](https://k9scli.io/) (optional, if you'd like to inspect your cluster visually) * [helm](https://helm.sh/) (optional, if you prefer to install the operator using the Helm chart) Note If not using kind, you will have to handle forwarding ports and exposing ingresses however appropriate for your distribution or infrastructure. ## Show me the commands ### Building our cluster OK, let's get our cluster up and running! We recommend using `kind` for this demo, but if you already have a K8s cluster, you can skip to [Install cert-manager](#install-cert-manager). #### Using Kind Download the cluster definition file, `kind-cluster-quick-start.yaml`: ```shell curl -sfL https://raw.githubusercontent.com/open-feature/openfeature.dev/main/static/samples/kind-cluster-quick-start.yaml > kind-cluster-quick-start.yaml ``` Then, create our cluster using the `kind-cluster-quick-start.yaml` file: ```shell kind create cluster --config kind-cluster-quick-start.yaml ``` This might take a minute or two. ### Install cert-manager Next, because our operator makes use of webhooks, we need some certificate infrastructure in our cluster. If your cluster already has cert manager, or you're using another solution for certificate management, you can go to [Install OpenFeature operator](#install-openfeature-operator). Install cert-manager, and wait for it to be ready: ```shell kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml && \ kubectl wait --timeout=60s --for condition=Available=True deploy --all -n 'cert-manager' ``` ### Install OpenFeature operator And finally, let's install the operator itself: Install using manifest ```shell kubectl apply -f https://github.com/open-feature/open-feature-operator/releases/download/v0.9.0/release.yaml && \ kubectl wait --timeout=60s --for condition=Available=True deploy --all -n 'open-feature-operator-system' ``` Install using Helm ```shell helm repo add openfeature https://open-feature.github.io/open-feature-operator/ && \ helm repo update && \ helm upgrade --install open-feature-operator openfeature/open-feature-operator ``` Note When using Helm, various configuration parameters can be set, such as resource limits and default configuration values. See the full [chart documentation](https://artifacthub.io/packages/helm/open-feature-operator/open-feature-operator#configuration) for details. ### Downloading assets Download the file defining our demo deployment, service and custom resource (CRs), `end-to-end.yaml`: ```shell curl -sfL https://raw.githubusercontent.com/open-feature/playground/main/config/k8s/end-to-end.yaml > end-to-end.yaml ``` ### Deploy our workload Now that the operator is ready to go, we can deploy our workload: (example below deploys into K8s default namespace, update accordingly to other namespace) ```shell kubectl -n default apply -f end-to-end.yaml && \ kubectl wait --timeout=60s deployment --for condition=Available=True -l 'app=open-feature-demo' -n 'default' ``` If you're using `k9s` or some other means of visualization, your cluster should look something like this: ![k9s](/assets/images/k9s-23f17d2121106ef1078a77a45244e5c9.png) ### Forward the service (if not using kind) ⚠️ If you're using the [supplied `kind` config](#using-kind), ***you can skip to [Experiment with OpenFeature](#experiment-with-openfeature), the ports are already forwarded.*** Forward the app service port: ```shell kubectl port-forward svc/open-feature-demo-app-service -n default 30000:30000 ``` Forward the UI flag evaluation service port: ```shell kubectl port-forward svc/open-feature-demo-ui-service -n default 30002:30002 ``` ## Experiment with OpenFeature Now you should see our fictional app at For this demo, we get flag definitions from the custom resource (CRs) you applied to K8s above (`end-to-end.yaml`). The resource type is `FeatureFlag` and there are two instances defined: one is called `ui-flags` (for the front-end) and one called `app-flags` (for the back end). Below, you'll see how you can modify these instances to change your feature flags. This file also contains service and deployment definitions, *but these need not be modified as part of this demo*. You may be interested in the `openfeature.dev/*` annotations though, which the OpenFeature operator uses to detect which workloads require flagd. * `openfeature.dev/enabled` - setting this to `true` instructs the operator to inject flagd as a sidecar * `openfeature.dev/featureflagsource` - refers to the `FeatureFlagSource` CRD, which defines flagd configurations, including its feature flag sources In the given example, there's a `FeatureFlagSource` custom resource (CR) named `flag-sources`, configured to use the `FeatureFlag` CRs mentioned above. In simple terms, you can think of a `FeatureFlagSource` instance as a resource that associates a workload with one or many `FeatureFlag` instances (though it has other purposes as well). You can learn more about these configurations from [flag source configuration documentation](https://flagd.dev/reference/openfeature-operator/crds/featureflagsource/) Next, let's get started learning how OpenFeature is helping Fib3r manage this landing page! The company has been in the process of changing the name of our app, but legal hasn't quite finished the process yet. Here, we've defined a simple feature flag that can be used to update the name instantly without redeploying our application. Change the `"defaultVariant"` of the feature flag `new-welcome-message"` to `"on"` in the `ui-flags` CR, then redeploy the change with: ```shell kubectl apply -n default -f end-to-end.yaml ``` Notice that the welcome message has changed from "Welcome to FaaS: Fibonacci as a Service!" to "Fib3r: Math at the speed of the internet!". Great! Now, let's help the design team experiment with a new color palette... Let's change the color of our landing page. Change the `"defaultVariant"` of the `"hex-color"` within the `ui-flags` CR and use `kubectl` to apply the change again. You should notice the color of the page changes immediately. Flag evaluations can take into account contextual information about the user, application, or action. The `"fib-algo"` flag returns a different result if our email ends with `"@faas.com"`. Let's run the fibonacci calculator... Run it once as a "customer" (without being logged in). Then log in as an "employee" (use any email ending in `...@faas.com`) and observe the impact. This effect is driven by the rule defined in the `app-flags` CR, which controls our server-side flags and is predicated on the email address of the user. Feel free to experiment with your own flag values and rules! ## Cleaning up If you used a kind cluster, you can clean everything up by running: ```shell kind delete cluster ``` --- # OpenFeature Specification ## Contents * [Glossary](https://openfeature.dev/specification/glossary.md) * [Types](https://openfeature.dev/specification/types.md) * [Evaluation API](https://openfeature.dev/specification/sections/flag-evaluation.md) * [Providers](https://openfeature.dev/specification/sections/providers.md) * [Evaluation Context](https://openfeature.dev/specification/sections/evaluation-context.md) * [Hooks](https://openfeature.dev/specification/sections/hooks.md) * [Events](https://openfeature.dev/specification/sections/events.md) * [Tracking](https://openfeature.dev/specification/sections/tracking.md) * [Appendix A: Included Utilities](https://openfeature.dev/specification/appendix-a.md) * [Appendix B: Gherkin Suites](https://openfeature.dev/specification/appendix-b.md) * [Appendix C: OFREP](https://openfeature.dev/specification/appendix-c.md) * [Appendix D: Observability](https://openfeature.dev/specification/appendix-d.md) * [Appendix E: Migrations](https://openfeature.dev/specification/appendix-e.md) ## Conformance ### Normative Sections The following parts of this document are normative: * Statements under markdown H5 headings, appearing in markdown block quotes, and containing an uppercase keyword from RFC 2119. * This conformance clause. ### Conformance Requirements and Test Assertions Each [normative section](#normative-sections) defines a single requirement. By enumerating these normative sections, a list of test assertions can be derived. ### Compliance An implementation is not compliant if it fails to satisfy one or more of the "MUST", "MUST NOT", "REQUIRED", "SHALL", or "SHALL NOT" requirements defined in the [normative sections](#normative-sections) of the specification. Conversely, an implementation of the specification is compliant if it satisfies all the "MUST", "MUST NOT", "REQUIRED", "SHALL", and "SHALL NOT" requirements defined in the [normative sections](#normative-sections) of the specification. ## Document Statuses Sections and subsections within the specification are marked with statuses indicating their stability level. Functionality described in the specification graduates through these statuses with increasing stability. Stability levels apply only to normative sections within the specification; editorial changes to examples and explanations are exempt from these constraints. It is the responsibility of the [Technical Steering Committee](https://github.com/open-feature/community/blob/main/governance-charter.md#tsc-members) to consider and approve the graduation of documents. Possible statuses are described below: ### Experimental [![experimental](https://img.shields.io/static/v1?label=Status\&message=experimental\&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) Specification sections that are marked as `Experimental` contain functionality under active development. Breaking changes are allowed and may be made without deprecation notices or warnings with minor version updates. We recommend you use these features in experimental environments and not in production. Put simply: > We're testing these features out. Things could change anytime. ### Hardening [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) Sections marked as `Hardening` describe functionality with an emphasis on stabilizing existing requirements. Breaking changes require consensus by the [Technical Steering Committee](https://github.com/open-feature/community/blob/main/governance-charter.md#tsc-members) but may still be made with minor version updates. These features are suitable for use in production environments. Feedback is encouraged. Put simply: > We believe these features are ready for production use, and hope for feedback. ### Stable [![stable](https://img.shields.io/static/v1?label=Status\&message=stable\&color=green)](https://github.com/open-feature/spec/tree/main/specification#stable) Sections marked as `Stable` do not allow breaking changes without a major version update. They can be used in production with a high degree of confidence. Put simply: > These features are stable and battle-hardened. Note No explicit status = `Experimental` --- # Appendix A: Included Utilities This document contains requirements for auxiliary utilities provided by the SDK, such as testing utilities. ## In-memory provider > OpenFeature SDK implementations **SHOULD** provide an `in-memory provider`. The in-memory provider is intended to be used for testing; SDK consumers may use it for their use cases. Hence, the packaging, naming, and access modifiers must be set appropriately. This provider **MUST** have the following features: * The provider is initiated with a pre-defined `flag set` provided in the constructor. * The flag structure must be minimalist and should help test the OpenFeature specification. * EvaluationContext support should be provided through callbacks/lambda expressions. * The provider must support a means of updating the `flag set`, resulting in the emission of `PROVIDER_CONFIGURATION_CHANGED` events. * The change event should consider all flags changed; a union of all previous and all new flag keys should be supplied in the `flags changed` field. * The provider must be maintained to support specification changes. ## SDK end-to-end testing > E2E tests must utilize [in-memory provider](#in-memory-provider) defined within the SDK and must be self-contained. OpenFeature project maintains an end-to-end(e2e) test suite defined with [Gherkin syntax](https://cucumber.io/docs/gherkin/). These test definitions reside in [Appendix B](https://openfeature.dev/specification/appendix-b.md) ```mermaid flowchart LR subgraph SDK A[e2e Tests] -.-> B[In-memory provider] end ``` ## Multi-Provider ### Introduction The OpenFeature Multi-Provider wraps multiple underlying providers in a unified interface, allowing the SDK client to transparently interact with all those providers at once. This allows use cases where a single client and evaluation interface is desired, but where the flag data should come from more than one source, or where tracking events should be sent to multiple providers simultaneously. Some examples: * **Migration**: When migrating between two providers, you can run both in parallel under a unified flagging interface. As flags are added to the new provider, the Multi-Provider will fetch flags from the new provider first, falling back to the old provider. * **Multiple Data Sources**: The Multi-Provider allows you to combine many sources of flagging data, such as environment variables, local files, database values and SaaS hosted feature management systems. * **Analytics Aggregation**: Send tracking events to multiple analytics providers simultaneously. Check the [OpenFeature JavaScript Multi-Provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/multi-provider) for a reference implementation. ### Basics The provider is initialized by passing a list of provider instances it should evaluate. The order of the array defines the order in which sources should be evaluated. The provider whose value is ultimately used will depend on the "strategy" that is provided, which can be chosen from a set of pre-defined ones or implemented as custom logic. For example: ```typescript const multiProvider = new MultiProvider( [ { provider: new ProviderA(), }, { provider: new ProviderB() } ], new FirstMatchStrategy() ) await OpenFeature.setProviderAndWait(multiProvider) ``` From the perspective of the SDK client, this provider will now act as a "normal" spec-compliant provider, while handling the complexities of aggregating multiple providers internally. ### Specific Behavior When dealing with many providers at once, various aspects of those providers need to be "combined" into one unified view. For example, each internal provider has a "status", which should influence the Multi-Provider's overall "status". The specific aspects that need to be addressed are described below. #### Unique Names In order to identify each provider uniquely, it must have a name associated with it. The unique name will be used when reporting errors and results in order to indicate from which provider they came from. Most providers have a `metadata.name` field which could be used, but this would be non-unique in the case where two instances of the same type of provider are used. As a result there would need to be a way to differentiate the two. When instantiating the Multi-Provider, there will be an option for specifying a name to associate to each provider: ```typescript const multiProvider = new MultiProvider([ { provider: new ProviderA(), name: "ProviderA" }, { provider: new ProviderB(), name: "ProviderB" } ]) await OpenFeature.setProviderAndWait(multiProvider) ``` Names for each provider are then determined like this: 1. name passed in to constructor if specified 2. `metadata.name` if it is unique among providers 3. `${metadata.name}_${index}` if name is not unique. Eg. the first instance of ProviderA provider might be named "providerA_1" and the second might be "providerA_2" If multiple names are passed in the constructor which conflict, an error will be thrown. #### Initialization Initialization of each provider should be handled in parallel in the Multi-Provider's `initialize` method. It should call `initialize` on each provider it is managing, and bubble up any error that is thrown by re-throwing to the client. #### Status and Event Handling The status of a provider is tracked in OpenFeature SDKs based on emitted events. Provider states can be transitioned in the ways represented here: The SDK client tracks statuses of a provider as follows: * Initially the status is `NOT_READY` * Initialize function is called (if exists) and result is awaited * Successful initialize transitions state to `READY`, error result transitions to either `ERROR` or `FATAL` * From this point onwards, status is only changed as a result of provider emitting events to indicate status-changing things have occurred. * It can emit events like `FATAL`, `ERROR`, `STALE` and `READY` to transition to those states. The only statuses which affect evaluation behavior at the SDK client level are `FATAL` and `NOT_READY`. If a provider is in either of these states, evaluation will be "skipped" by the client and the default value will be returned. Other statuses are currently "informational". Nevertheless, the Multi-Provider will represent an overall "status" based on the combined statuses of the providers. ##### Multi-Provider Status The Multi-Provider mimics the event handling logic that tracks statuses in the SDK, and keeps track of the status of each provider it is managing. The individual status-changing events from these providers will be "captured" in the Multi-Provider, and not re-emitted to the outer SDK UNLESS they cause the status of the Multi-Provider to change. The status of the Multi-Provider will change when one of its providers changes to a status that is considered higher "precedence" than the current status. The precedence order is defined as: * FATAL * NOT_READY * ERROR * STALE * READY For example, if all providers are currently in `READY` status, the Multi-Provider will be in `READY` status. If one of the providers is `STALE`, the status of the Multi-Provider will be `STALE`. If a different provider now becomes `ERROR`, the status will be `ERROR` even if the other provider is still in `STALE`. When the Multi-Provider changes status, it does so by emitting the appropriate event to the SDK. The "details" of that event will be **identical** to the details of the original event from one of the inner providers which triggered this state change. There is another event called "configuration changed" which does not affect status. This event should be re-emitted any time it occurs from any provider. #### Evaluation Result The evaluation result is based on the results from evaluating each provider. There are multiple "strategies" configurable in the Multi-Provider to decide how to use the results. #### Track Method Support The Multi-Provider implements the `track` method from the OpenFeature specification, allowing tracking events to be sent across multiple underlying providers. By default, tracking events are sent to all providers that are in `READY` status. Providers in `NOT_READY` or `FATAL` states are automatically skipped for tracking operations. Key features of tracking support include: * **Error Resilience**: Individual provider tracking failures do not break the overall tracking flow - errors are logged but do not throw exceptions * **Status Awareness**: Providers in `NOT_READY` or `FATAL` status will not recieve tracking events. * **Strategy Integration**: Custom strategies can control which providers receive tracking calls using the `shouldTrackWithThisProvider` method * **Graceful Degradation**: Providers that don't implement the `track` method are skipped Example usage: ```typescript const multiProvider = new MultiProvider([ { provider: new ProviderA() }, { provider: new ProviderB() } ]) await OpenFeature.setProviderAndWait(multiProvider) const client = OpenFeature.getClient() // Track events across all ready providers client.track('purchase', { targetingKey: 'user123' }, { value: 99.99, currency: 'USD' }) ``` For custom tracking behavior, strategies can implement the `shouldTrackWithThisProvider` method to selectively track with specific providers based on event name, context, or provider characteristics. #### Interpreting Errors Currently, providers have multiple ways of signalling evaluation errors to the SDK. Particularly in the case of Javascript, a provider can return an evaluation result that contains an error code and message, but still has a "value" for the result. It can also throw an error. Several providers currently use the former approach for indicating errors in operations, and use the `value` field of the result to return the default value from the provider itself. For the purposes of aggregating providers, the Multi-Provider treats both thrown and returned errors as an "error" result. If the returned error result has a value, that value will be ignored by all strategies. Only "nominal" evaluation results will be considered by the evaluation. ### Strategies The Multi-Provider supports multiple ways of deciding how to evaluate the set of providers it is managing, and how to deal with any errors that are thrown. Strategies must be adaptable to the various requirements that might be faced in a multi-provider situation. In some cases, the strategy may want to ignore errors from individual providers as long as one of them successfully responds. In other cases, it may want to evaluate providers in order and skip the rest if a successful result is obtained. In still other scenarios, it may be required to always call every provider and decide what to do with the set of results. The strategy to use is passed in to the Multi-Provider constructor as follows: ```typescript new MultiProvider( [ { provider: new ProviderA() }, { provider: new ProviderB() } ], new FirstMatchStrategy() ) ``` By default, the Multi-Provider uses the `FirstMatchStrategy`. Here are some standard strategies that come with the Multi-Provider: #### First Match Strategy Return the first result returned by a provider. Skip providers that indicate they had no value due to `FLAG_NOT_FOUND`. In all other cases, use the value returned by the provider. If any provider returns an error result other than `FLAG_NOT_FOUND`, the whole evaluation should error and "bubble up" the individual provider's error in the result. As soon as a value is returned by a provider, the rest of the operation should short-circuit and not call the rest of the providers. [See the refrence implementation](https://github.com/open-feature/js-sdk-contrib/blob/main/libs/providers/multi-provider/src/lib/strategies/FirstMatchStrategy.ts) #### First Successful Strategy Similar to "First Match", except that errors from evaluated providers do not halt execution. Instead, it will return the first successful result from a provider. If no provider successfully responds, it will throw an error result. [See the refrence implementation](https://github.com/open-feature/js-sdk-contrib/blob/main/libs/providers/multi-provider/src/lib/strategies/FirstSuccessfulStrategy.ts) #### Comparison Strategy Require that all providers agree on a value. If every provider returns a non-error result, and the values do not agree, the Multi-Provider should return the result from a configurable "fallback" provider. It will also call an optional "onMismatch" callback that can be used to monitor cases where mismatches of evaluation occurred. Otherwise the value of the result will be the result of the first provider in precedence order. [See the refrence implementation](https://github.com/open-feature/js-sdk-contrib/blob/main/libs/providers/multi-provider/src/lib/strategies/ComparisonStrategy.ts) #### User Defined Custom Strategy Rather than making assumptions about when to use a provider's result and when not to (which may not hold across all providers) there is also a way for the user to define their own strategy that determines whether or not to use a result or fall through to the next one. A strategy can be implemented by implementing the `BaseEvaluationStrategy` class as follows: ```typescript type StrategyEvaluationContext = { flagKey: string; flagType: FlagValueType; }; type StrategyPerProviderContext = StrategyEvaluationContext & { provider: Provider; providerName: string; providerStatus: ProviderStatus; }; type ProviderResolutionResult = { details: ResolutionDetails; thrownError?: unknown; provider: Provider; providerName: string; } type FinalResult = { details?: ResolutionDetails; provider?: Provider; providerName?: string; errors?: { providerName: string; error: unknown; }[]; }; abstract class BaseEvaluationStrategy { runMode: 'parallel' | 'sequential' abstract shouldEvaluateThisProvider( strategyContext: StrategyPerProviderContext, evalContext: EvaluationContext ): boolean; abstract shouldEvaluateNextProvider( strategyContext: StrategyPerProviderContext, context: EvaluationContext, result: ProviderResolutionResult ): boolean; abstract determineFinalResult( strategyContext: StrategyEvaluationContext, context: EvaluationContext, resolutions: ProviderResolutionResult[], ): FinalResult; abstract shouldTrackWithThisProvider( strategyContext: StrategyProviderContext, context: EvaluationContext, trackingEventName: string, trackingEventDetails: TrackingEventDetails, ): boolean; } ``` * **`runMode`**: property defines whether the providers will all be evaluated at once in `parallel`, or whether they will be evaluated `sequentially` with each result determining whether to evaluate the next one in order. * **`shouldEvaluateThisProvider`**: function is called for each provider right before the Multi-Provider would evaluate it. * If the function returns false, the provider will be skipped. * This can be useful in cases where it's desired to skip a provider based on what flag key is being used, or based on some state from the provider itself that indicates it shouldn't be evaluated right now. * **`shouldEvaluateNextProvider`**: function is called right after a provider is evaluated. * It is called with the details of resolution or any error that was thrown (which will be caught). * If the function returns true, the next provider will be called. * Otherwise all remaining providers will be skipped and the results of the ones that have been evaluated so far will be passed to `determineFinalResult` . * If this function throws an error, the Multi-Provider will throw an error and not evaluate further providers. * This function is not called when `runMode` is `parallel`, since all providers will be executed (as long as they individually pass the `shouldEvaluateThisProvider` check) * **`determineFinalResult`**: function is called after the resolution stage if no further providers will be called. * This function can be used to decide from the set of resolutions which one should ultimately be used. * The function must return a `FinalResult` object which contains the final `ResolutionDetails` and the provider that they correspond to, or an array of `errors` in the case of a non-successful result, with the provider that created each error. * **`shouldTrackWithThisProvider`**: function is called when the `track()` method is called on the SDK. * This function can be used to decide which providers should recieve `track()` events. * Return `true` to send the tracking event to the provider, `false` to skip it. To see [reference implementations](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/multi-provider/src/lib/strategies) of the above-mentioned strategies. ### Hooks Provider hooks are capable of modifying the context before an evaluation takes place. This behavior must be preserved, but it's also necessary to prevent these hooks from interfering with the context being passed to other providers. For this reason, the Multi-Provider manages calling the hooks of each provider itself, at the appropriate time. It then uses the result of the before hooks for a given provider as the new evaluation context when evaluating **that provider**, without affecting the context used for other providers. It then calls the after, error and finally hooks using the appropriate context as well. Errors thrown from these hooks are be bubbled up to the client, depending on how the evaluation "strategy" defines what to do with errors. ### Shutdown The shutdown method should ensure that `shutdown()` is called in all underlying providers, and bubble up any errors to the client ### Error Handling In cases where all providers are being called (Evaluation etc.) there may be more than one error encountered from more than one provider. The Multi-Provider will collect and throw all errors in an aggregated form as follows: ```javascript error = { message: 'some message', code: SOME_ERROR, // which provider caused the error originalErrors: [ { source: 'ProviderA', error: { message: 'something', } } ] } ``` In the case where only one error is thrown by one provider, it will still throw in this form for consistency. Other errors from the Multi-Provider itself will use standard error types. ### Metadata Providers can contain metadata. The Multi-Provider will make that metadata available within its own metadata as follows: ```javascript { name: 'multiprovider', originalMetadata: { providerA: {...}, providerB: {...} }, } ``` ## Logging Hook > OpenFeature SDK implementations **SHOULD** provide a `logging hook`. The logging hook is a hook which logs messages during the flag evaluation life-cycle as described below: | Stage | Logged data | | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | before | `stage`, `domain`, `provider_name`, `flag_key`, `default_value` and `evaluation_context` (serialized, opt-in) | | after | `stage`, `domain`, `provider_name`, `flag_key`, `default_value`, `evaluation_context` (serialized, opt-in), `reason`, `variant` and `value` | | error | `stage`, `domain`, `provider_name`, `flag_key`, `default_value`, `evaluation_context` (serialized, opt-in), `error_code`, and `error_message` | | finally | N/A | > The evaluation context **SHOULD** only be logged if an associated option indicates so. This can be a constructor option or similar, for example: `boolean printContext`. > If logging the evaluation context is enabled, it **MUST** be printed in such a way that it's human readable. Consider printing the evaluation context as a stringified JSON object, or using some other format that allows the nested properties to be easily read. > If the logger abstraction in the SDK supports a log level concept, the appropriate log level **SHOULD** be used for each stage (before/after: debug/info, error: error). Consider using `debug` or `info` levels for the `before` and `after` stages, and the `error` level for the `error` stage. --- # Appendix B: Gherkin Suites This section contains a set of language-agnostic end-to-end tests (defined in gherkin). These tests can be used to validate the behavior of an OpenFeature implementation. "Features" (test suites) can be used in conjunction with an [in-memory provider](https://openfeature.dev/specification/appendix-a.md#in-memory-provider) and a cucumber test-runner for the language in question. ## Evaluation Feature The [evaluation feature](https://openfeature.dev/assets/files/evaluation-cc96ecd28da58ca4598330cbe4c051f5.feature) contains tests for the basic functionality of the [Evaluation API](https://openfeature.dev/specification/sections/flag-evaluation.md). --- # Appendix C: OpenFeature Remote Evaluation Protocol OpenFeature Remote Evaluation Protocol (OFREP) is a unified feature flag evaluation protocol that adheres to the OpenFeature semantics. You can find proposals, discussions and the progress at the dedicated [OpenFeature OFREP](https://github.com/open-feature/protocol) repository. There is a dedicated [OpenFeature working group](https://github.com/open-feature/community/blob/main/config/open-feature/spec-evaluation/workgroup.yaml) for this initiative that focuses on: * Feature flag evaluation protocol * Telemetry enrichment of the evaluations * Reference implementations of the protocol If you are interested in this initiative, you can join the [`#openfeature-remote-evaluation-protocol`](https://cloud-native.slack.com/archives/C066A48LK35) Slack channel on the [CNCF Slack](https://slack.cncf.io/) workspace. ## API Specification View the full [OFREP API Specification](https://openfeature.dev/docs/reference/other-technologies/ofrep/openapi) with interactive documentation. --- # Appendix D: Observability This document describes conventions for extracting data from the OpenFeature SDK for use in telemetry signals. It primarily focuses on providing recommendations for mapping well-known fields in OpenFeature to [OpenTelemetry feature-flag event records](https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/) and other semantic conventions. ## Evaluations Flag evaluation telemetry comprises data resolved from the provider resolution (evaluation details and flag metadata) as well as metadata about the provider itself. This is particularly relevant to telemetry-related [hooks](https://openfeature.dev/specification/sections/hooks.md). ### Evaluation Details The following describes how fields on the [evaluation details](https://openfeature.dev/specification/types.md#evaluation-details) are mapped to feature flag event records: | Event Record Attribute | Source Field or Derived Value from Evaluation Details | Requirement level | Type | Notes | | ----------------------------- | ----------------------------------------------------- | ------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------- | | `feature_flag.key` | `flag key` | `Required` | `string` | See: [flag key](https://openfeature.dev/specification/glossary.md#flag-key) | | `feature_flag.result.variant` | `variant` | `Conditionally Required` [1](#user-content-fn-1) | `string` | See: [variant](https://openfeature.dev/specification/glossary.md#variant) | | `feature_flag.result.value` | `value` | `Conditionally Required` [2](#user-content-fn-2) | `undefined` | See: [value](https://openfeature.dev/specification/glossary.md#values) | | `feature_flag.result.reason` | `reason` | `Recommended` | `string` | See: [reason](https://openfeature.dev/specification/types.md#resolution-reason) | | `error.type` | `error code` | `Conditionally Required` [3](#user-content-fn-3) | `string` | See: [error code](https://openfeature.dev/specification/types.md#error-code), | | `error.message` | `error message` | `Conditionally Required` [3](#user-content-fn-3) | `string` | A human-readable error message associated with a failed evaluation. For programmatic purposes, refer to `error code`. | > \[!NOTE] The `error.type` and `feature_flag.result.reason` enumerations use a lowercase "snake_case" convention (see [OpenTelemetry feature-flag event records](https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/)). OpenFeature [error codes](https://openfeature.dev/specification/types.md#error-code) and [resolution reasons](https://openfeature.dev/specification/types.md#resolution-reason) should be transformed accordingly by integrations which include this data. ### Flag Metadata The following describes how keys in [flag metadata](https://openfeature.dev/specification/types.md#flag-metadata) are mapped to feature flag event records: | Event Record Attribute | Flag Metadata Key | Requirement level | Type | Notes | | ------------------------- | ----------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `feature_flag.context.id` | `contextId` | `Recommended` | `string` | The context identifier returned in the flag metadata uniquely identifies the subject of the flag evaluation. If not available, the [targeting key](https://openfeature.dev/specification/glossary.md#targeting-key) should be used. | | `feature_flag.set.id` | `flagSetId` | `Recommended` | `string` | A logical identifier for the [flag set](https://openfeature.dev/specification/glossary.md#flag-set). | | `feature_flag.version` | `version` | `Recommended` | `string` | A version string (format unspecified) for the flag or [flag set](https://openfeature.dev/specification/glossary.md#flag-set). | > \[!NOTE] Keys in flag metadata use the "camelCase" casing convention, while the OpenTelemetry standard uses a namespaced "snake_case" convention. ### Provider Metadata | Event Record Attribute | Provider Metadata Field | Requirement level | Type | Notes | | ---------------------------- | ----------------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------ | | `feature_flag.provider.name` | `name` | `Recommended` | `string` | The name of the provider as defined in the `provider metadata`, available in the `hook context`. | ## Telemetry Hook Implementation Guide This section provides guidance for implementing observability hooks that emit OpenTelemetry signals during feature flag evaluations. The recommendations ensure consistency across SDK implementations while allowing for language-specific idioms. ### Signal Emission Patterns Telemetry hooks can emit OpenTelemetry signals in three distinct ways: | Pattern | Advantages | Disadvantages | | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Span Events**
![recommended](https://img.shields.io/badge/recommended-green) | - Leverages existing trace configuration and tooling
- Minimal overhead, no additional spans created
- Maintains trace context relationships
- Simpler than creating spans | - Requires an active span to function
- Must gracefully handle absence of active span
- Limited to span lifetime and context. | | **Event Logging** | - Works independently without active spans
- Aligns with OpenTelemetry's emerging direction
- Suitable for environments without tracing
- Simpler implementation model | - Requires an event exporter to be configured
- Processed and stored separately from spans
- Event logging standards still evolving | | **Standalone Spans** | - Distributed traces contain every evaluation
- Detailed timing information
- Full span lifecycle control | - Creates one span per evaluation
- May clutter trace visualizations
- Increased overhead and resource usage
- Potential performance impact at scale
- More complex implementation | > \[!NOTE] While span events are recommended for their low overhead and ease of use, OpenTelemetry is trending toward using log-based events instead of span events. Please refer to the [OpenTelemetry Span Event Deprecation Plan](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4430-span-event-api-deprecation-plan.md) for more details. ### Hook Lifecycle Implementation #### Before Stage The `before` hook stage is primarily used by standalone span hooks to create and store spans. When creating spans, it's recommended to use the name `feature_flag.evaluation` and store them in hook data using a consistent, documented key for easy retrieval in later stages. #### Error Stage The `error` hook stage records exception information unless explicitly configured to exclude it. Implementations typically use [OpenTelemetry's standard exception](https://opentelemetry.io/docs/specs/semconv/general/recording-errors/) recording semantics (`recordException` for spans, exception log events for event logging). Configuration options like `excludeExceptions` allow users to control this behavior based on their needs. #### Finally Stage The `finally` hook stage is where telemetry signals are emitted with complete evaluation details. This stage should include all required and conditionally required attributes as defined in the attribute mapping tables above. It's also responsible for proper resource cleanup (like ending spans or closing connections). ### Attribute Transformations When building telemetry attributes, implementations should extract and map well-known fields from flag metadata to their corresponding event record attributes as defined in the Flag Metadata table above. Remember to transform enumeration values (like error codes and reasons) from OpenFeature's uppercase format to OpenTelemetry's lowercase snake_case convention. ### Value Handling and Privacy Flag values can contain large or sensitive data, so implementations should provide configuration to control whether values are included in telemetry signals. It's the users' responsibility to manage this configuration. When values are included, they need to be serialized appropriately for OpenTelemetry. Consider providing mechanisms to redact or obfuscate sensitive flag values, along with size limits to prevent telemetry bloat. This helps balance observability needs with privacy and performance concerns. ### Configuration Options For consistency across implementations, consider supporting a common set of configuration options: * `attributeMapper` (function): Custom function to add additional attributes to the signal * `excludeAttributes` (list): List of attribute keys to exclude from the signal * `excludeExceptions` (boolean): Whether to omit [exception details](https://opentelemetry.io/docs/specs/semconv/exceptions/) from error signals * `eventMutator` (function): Custom function to modify event attributes before sending ### Error Handling Hooks should be designed to never throw exceptions that interrupt flag evaluation. Any internal errors can be logged at appropriate levels (debug/trace) without affecting application execution. ### Implementation Patterns #### Common Base Class In object-oriented languages, you might find it helpful to create a base hook class containing common functionality shared across all telemetry hook types. This typically includes: * Shared configuration options * Attribute building and transformation methods * Enumeration format conversion * Metadata extraction logic * Logger instances for internal debugging This pattern can reduce code duplication and ensure consistency across different hook implementations, though it's not required. ## History Feature flags in the OpenTelemetry semantic conventions are currently in development and are marked as a release candidate. The following table describes the history of changes to the OpenTelemetry feature flag event records as it progresses towards a stable release. | Original Field Name | New Field Name | Semantic Convention Release | | --------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------- | | `feature_flag.variant` | `feature_flag.result.variant` | [v1.32.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.32.0) | | `feature_flag.evaluation.reason` | `feature_flag.result.reason` | [v1.32.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.32.0) | | `feature_flag.evaluation.error.message` | `error.message` | [v1.33.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.33.0) | | `feature_flag.provider_name` | `feature_flag.provider.name` | [v1.33.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.33.0) | | `value` | `feature_flag.result.value` | [v1.34.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.34.0) | ## Footnotes ## Footnotes 1. The `variant` field should be included whenever possible as it represents the symbolic name of the flag's returned value (e.g., "on"/"off", "control"/"treatment"). Only omit if the provider doesn't supply this information. [↩](#user-content-fnref-1) 2. The `value` field is required when a `variant` is not available, and recommended when it is. Considerations should be made for large and/or sensitive values, which should be redacted or omitted prior to being captured in telemetry signals. [↩](#user-content-fnref-2) 3. Include `error.type` and `error.message`, if and only if an error occurred during a flag evaluation. [↩](#user-content-fnref-3) [↩2](#user-content-fnref-3-2) --- # Appendix E: Migrations This appendix provides non-normative guidance for provider authors and SDK authors on migrating to new or changed specification requirements. ## Provider Lifecycle Event Emission ### Background Prior to `v0.9.0`, the SDK emitted synthetic lifecycle events (`PROVIDER_READY`, `PROVIDER_ERROR`) on behalf of providers after lifecycle methods (`initialize`, `shutdown`, `on context changed`) returned. This created a race condition in multi-threaded SDKs: the provider could emit events from background threads concurrently with SDK-emitted synthetic events, resulting in incorrect event ordering and inconsistent status. The spec now requires providers to emit their own lifecycle events. The SDK derives provider status entirely from the provider's event stream (see [provider status](https://openfeature.dev/specification/sections/providers.md#28-provider-status), [requirement 5.3.5](https://openfeature.dev/specification/sections/events.md#requirement-535)). ### For provider authors Providers must now emit events for all state transitions, including those resulting from lifecycle methods: * `PROVIDER_READY` after successful initialization * `PROVIDER_ERROR` after failed initialization (with appropriate error code) * `PROVIDER_RECONCILING` when beginning context reconciliation (static-context paradigm) * `PROVIDER_CONTEXT_CHANGED` after successful context reconciliation (static-context paradigm) * `PROVIDER_ERROR` after failed context reconciliation (static-context paradigm) To signal to the SDK that your provider emits its own lifecycle events, implement the opt-in marker defined by the SDK (e.g. an interface, boolean property, or type-level tag). Providers that do not implement this marker will continue to work via the SDK's legacy compatibility path (see below). Providers with no `initialize` function are handled per [Condition 2.8.5](https://openfeature.dev/specification/sections/providers.md#condition-285) and require no changes. #### Common migration patterns **Blocking initialization.** If `initialize` performs setup synchronously and returns when ready, emit `PROVIDER_READY` immediately before returning (or `PROVIDER_ERROR` before throwing). **Event-driven initialization.** If setup completes asynchronously (e.g. the underlying vendor SDK signals readiness via its own event), keep `initialize` pending until that signal arrives, emit the corresponding `PROVIDER_READY` or `PROVIDER_ERROR` event, then return or throw. **Context reconciliation.** The provider now owns the coalescing behavior previously handled by the SDK. If `on context changed` may be invoked simultaneously or in quick succession, emit `PROVIDER_CONTEXT_CHANGED` (or `PROVIDER_ERROR`) only after the last reentrant invocation terminates. #### Anti-patterns * Do not emit `PROVIDER_READY` before initialization work is complete. * Do not terminate `initialize` before emitting the corresponding `PROVIDER_READY` or `PROVIDER_ERROR` event. ### For SDK authors SDKs must detect whether a provider emits its own lifecycle events, via an opt-in marker (e.g. an interface, boolean property, or type-level tag). * **Marker present:** the SDK does not emit synthetic lifecycle events. It derives status solely from the provider's event stream. * **Marker absent (legacy):** the SDK emits synthetic lifecycle events after lifecycle methods return, as before. This path is deprecated. The legacy path should be deprecated in the release that introduces the marker, with removal targeted for the next major version. SDK authors should update any first-party providers and provider base classes to emit their own lifecycle events. #### Legacy path behavior For providers without the marker, the SDK preserves pre-`v0.9.0` behavior: * Emit `PROVIDER_READY` after `initialize` returns normally. * Emit `PROVIDER_ERROR` (with the returned error code, if any) after `initialize` returns abnormally. * For static-context providers: coalesce reentrant `on context changed` invocations and emit `PROVIDER_CONTEXT_CHANGED` or `PROVIDER_ERROR` after the last one terminates. If a legacy provider also emits its own events, the SDK processes them per [requirement 5.3.5](https://openfeature.dev/specification/sections/events.md#requirement-535); duplicates are possible in this mixed mode and are expected legacy behavior. SDKs should log a deprecation warning on registration of a legacy provider. --- # Glossary This document defines some terms that are used across this specification. Table of Contents * [Feature Flag](#feature-flag) * [User Roles](#user-roles) * [Application Author](#application-author) * [Application Integrator](#application-integrator) * [Provider Author](#provider-author) * [Integration Author](#integration-author) * [Library Author](#library-author) * [Common](#common) * [Feature Flag SDK](#feature-flag-sdk) * [Client-Side SDK](#client-side-sdk) * [Server-Side SDK](#server-side-sdk) * [Feature Flag API](#feature-flag-api) * [Evaluation API](#evaluation-api) * [Flag Management System](#flag-management-system) * [Client](#client) * [Provider](#provider) * [Provider Lifecycle](#provider-lifecycle) * [Domain](#domain) * [Isolated API Instance](#isolated-api-instance) * [Integration](#integration) * [Evaluation Context](#evaluation-context) * [Transaction Context Propagator](#transaction-context-propagator) * [Evaluating Flag Values](#evaluating-flag-values) * [Resolving Flag Values](#resolving-flag-values) * [Tracking Event](#tracking-event) * [Flagging specifics](#flagging-specifics) * [Flag](#flag) * [Flag Set](#flag-set) * [Flag Key](#flag-key) * [Variant](#variant) * [Values](#values) * [Targeting](#targeting) * [Targeting Key](#targeting-key) * [Fractional Evaluation](#fractional-evaluation) * [Rule](#rule) * [SDK Paradigms](#sdk-paradigms) * [Dynamic-Context Paradigm](#dynamic-context-paradigm) * [Static-Context Paradigm](#static-context-paradigm) ## Feature Flag A mechanism that allows an Application Author to define alternative codepaths within a deployed piece of software, which is conditionally executed at runtime, based on a rule set. ## User Roles ### Application Author A developer of an application or service which utilizes the feature flags SDK. This person writes code which calls into the SDK to make flagging decisions. ### Application Integrator A developer who is setting up or configuring an application or service to use the feature flags SDK. They would write code like "We should speak to the open source flagging service, not $vendor" or "The way the system should handle telemetry is through $library". ### Provider Author The maintainer of an API-compliant [provider](https://openfeature.dev/specification/sections/providers.md) which implements the necessary interfaces required for flag evaluation. ### Integration Author The maintainer of an API-compliant integration which implements additional secondary functionality besides flag evaluation. ### Library Author The maintainer of a shared library which is a dependency of many applications or libraries, which utilizes the feature flags SDK to allow consumers to manage library functionality. ## Common ### Feature Flag SDK The libraries used by the Application Author to implement feature flags in their application or service. The interfaces defined in these libraries adhere to the Feature Flag API. ### Client-Side SDK An SDK which is built for usage in client applications (e.g. single-page web applications), and typically uses the [static-context paradigm](#static-context-paradigm). ### Server-Side SDK An SDK which is built for usage in server applications (e.g. REST services), and typically uses the [dynamic-context paradigm](#dynamic-context-paradigm). ### Feature Flag API The interfaces and abstractions used by authors (Application, Integration, Provider). Provider & Integration authors adhere to the API to add support for their feature flag implementation or integration. Application authors use it via the Feature Flag SDK. ### Evaluation API The subset of the [Feature Flag API](#feature-flag-api) that the Application Author uses to evaluate flags. ### Flag Management System A source-of-truth for flag values and rules. Flag management systems may include SaaS feature flag vendors, custom "in-house" feature flag infrastructure, or open-source implementations. ### Client A lightweight abstraction that provides functions to evaluate feature flags. A client is associated with a single provider, which it uses to perform evaluations. ### Provider An SDK-compliant implementation which resolves flag values from a particular flag management system, allowing the use of the [Evaluation API](https://openfeature.dev/specification/sections/flag-evaluation.md#13-flag-evaluation) as an abstraction for the system in question. ### Provider Lifecycle The possible states and transitions of a provider over the course of its usage, as defined by the [provider interface](https://openfeature.dev/specification/sections/providers.md). ### Domain An identifier which logically binds clients with providers, allowing for multiple providers to be used simultaneously within a single application. Domain binding is dynamic; it may change over the course of an application's lifetime (i.e.: a client associated with the default provider via an unbound domain will be bound to a new provider if a provider is subsequently assigned to that domain). ### Isolated API Instance An independent, non-singleton instance of the [Feature Flag API](#feature-flag-api) created via a factory function. Each isolated instance maintains its own state, including providers, evaluation context, hooks, and event handlers. Isolated instances do not share state with the global singleton or with each other. Intended for advanced use cases such as micro-frontend architectures, dependency injection frameworks, and testing scenarios. ### Integration An SDK-compliant secondary function that is abstracted by the Feature Flag API, and requires only minimal configuration by the Application Author. Examples include telemetry, tracking, custom logging and monitoring. ### Evaluation Context Context object for flag evaluation, which may contain information about the runtime environment, details of the transport method encapsulating the flag evaluation, the host, the client, the subject (user), etc. This data may be used as a basis for differential evaluation of feature flags based on rules that can be defined in the flag system. Context data may be provided by merging static global context, arguments to flag evaluation, and implicit language-dependant state propagation mechanisms (thread-local storage, promise chains, continuations, etc). ### Transaction Context Propagator An SDK-compliant implementation that stores and returns transaction-specific evaluation context. A *transaction* might be a web request or application event, which carries its contextual data in a thread or continuation storage. ### Evaluating Flag Values The process of retrieving a feature flag value in its entirety, including: * any effects resulting from hooks * resolving a flag value from a configured provider * falling back to a supplied default, in the case of abnormal execution ### Resolving Flag Values The process of a provider retrieving a feature flag value from its particular source-of-truth. ### Tracking Event A particular user action or application state representing a business objective or outcome, identified by a unique string, and recorded using the [tracking API](https://openfeature.dev/specification/sections/tracking.md). ## Flagging specifics ```mermaid erDiagram Flag ||--|{ variant: "has many" variant ||--|| value: "has" Flag ||--o{ rule: "Has zero or many" rule }|--|{ variant : "links to many" ``` ### Flag Flags represent a single pivot point of logic. Flags have a type, like `string`, `boolean`, `json`, etc. Examples: `redesign_enabled` or `header-order` ### Flag Set A collection of related [flags](#flag). This grouping helps organize feature flags based on their intended use, facilitating easier management and deployment. ### Flag Key A string that logically identifies a particular flag. ### Variant A variant is a semantic identifier for a value. This allows for referral to particular values without necessarily including the value itself, which may be quite prohibitively large or otherwise unsuitable in some cases. ### Values Individual variants have values associated with them. These values adhere to the flag's type. For the `header-order` variants, we may have values like: ```text reverse: [5,4,3,2,1] wonky: [3,5,2,1,4] standard: [1,2,3,4,5] ``` ### Targeting The application of rules, specific user overrides, or fractional evaluations in feature flag resolution. ### Targeting Key A string logically identifying the subject of evaluation (end-user, service, etc). ### Fractional Evaluation Pseudorandomly resolve flag values using a context property, such as a targeting key, based on a configured proportion or percentage (ie: 50/50). ### Rule A rule is some criteria or logic used to assign a variant during an evaluation. ## SDK Paradigms Feature flag frameworks have SDKs which operate in two distinct paradigms: those designed for use with a single user client application (e.g. mobile phones, single-page web apps), and those designed for multi-user applications, such as web server applications. Some parts of the OpenFeature specification diverge depending on the paradigm. ### Dynamic-Context Paradigm Server-side applications typically perform flag evaluations on behalf of many users, with each request or event being associated with a particular user or client. For this reason, server frameworks typically operate similarly to this: * the application is initialized with some static context (geography, service name, hostname, etc) * with each request or event, relevant dynamic context (for example, user session data, unique user identifiers) is provided to flag evaluations ### Static-Context Paradigm In contrast to server-side or other service-type applications, client side applications typically operate in the context of a single user. Most feature flagging libraries for these applications have been designed with this in mind. Frequently, client/web libraries operate similarly to this: * an initialization occurs, which fetches evaluated flags in bulk for a given context (user) * the evaluated flags are cached in the library * flag evaluations take place against this cache, without a need to provide context (context was already used to evaluate flags in bulk) * libraries provide a mechanism to update context (e.g. if a user logs in), meaning cached evaluations are no longer valid and must be re-evaluated, frequently involving a network request or I/O operation Not all client libraries work this way, but generally, libraries that accept dynamic context per evaluation can build providers which conform to this model with relative ease, while the reverse is not true. --- # 3. Evaluation Context [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ## Overview The `evaluation context` provides ambient information for the purposes of flag evaluation. Contextual data may be used as the basis for targeting, including rule-based evaluation, overrides for specific subjects, or fractional flag evaluation. The context might contain information about the end-user, the application, the host, or any other ambient data that might be useful in flag evaluation. For example, a flag system might define rules that return a specific value based on the user's email address, locale, or the time of day. The context provides this information. The context can be optionally provided at evaluation, and mutated in [before hooks](https://openfeature.dev/specification/sections/hooks.md). ### 3.1 Fields Note Field casing is not specified and should be chosen in accordance with language idioms. see: [types](https://openfeature.dev/specification/types.md) #### Requirement 3.1.1 > The `evaluation context` structure **MUST** define an optional `targeting key` field of type string, identifying the subject of the flag evaluation. The targeting key uniquely identifies the subject (end-user, or client service) of a flag evaluation. Providers may require this field for fractional flag evaluation, rules, or overrides targeting specific users. Such providers may behave unpredictably if a targeting key is not specified at flag resolution. #### Requirement 3.1.2 > The evaluation context **MUST** support the inclusion of custom fields, having keys of type `string`, and values of type `boolean | string | number | datetime | structure`. see: [structure](https://openfeature.dev/specification/types.md#structure), [datetime](https://openfeature.dev/specification/types.md#datetime) #### Requirement 3.1.3 > The evaluation context **MUST** support fetching the custom fields by key and also fetching all key value pairs. #### Requirement 3.1.4 > The evaluation context fields **MUST** have a unique key. The key uniquely identifies a field in the `evaluation context` and it should be unique across all types to avoid any collision when marshalling the `evaluation context` by the provider. ### 3.2 Context levels and merging #### Condition 3.2.1 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 3.2.1.1 > The API, Client and invocation **MUST** have a method for supplying `evaluation context`. API (global) `evaluation context` can be used to supply static data to flag evaluation, such as an application identifier, compute region, or hostname. Client and invocation `evaluation context` are ideal for dynamic data, such as end-user attributes. #### Condition 3.2.2 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 3.2.2.1 > The API **MUST** have a method for setting the global `evaluation context`. API (global) `evaluation context` can be used to supply data to flag evaluation, such as (but not limited to) user name, email, or user organization membership changes. ##### Conditional Requirement 3.2.2.2 > The Client and invocation **MUST NOT** have a method for supplying `evaluation context`. In the static-context paradigm, context is global. The client and invocation cannot supply evaluation context. ##### Conditional Requirement 3.2.2.3 > The API **MUST** have a method for setting `evaluation context` for a `domain`. In the static-context paradigm, provider specific context can be set using the associated `domain`. The global context is used if there is no matching provider specific context. See [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. ##### Conditional Requirement 3.2.2.4 > The API **MUST** have a mechanism to manage `evaluation context` for an associated `domain`. In the static-context paradigm, it's possible to create and remove provider-specific context. See [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. #### Requirement 3.2.3 > Evaluation context **MUST** be merged in the order: API (global; lowest precedence) -> transaction -> client -> invocation -> before hooks (highest precedence), with duplicate values being overwritten. Any fields defined in the transaction `evaluation context` will overwrite duplicate fields defined in the global `evaluation context`, any fields defined in the client `evaluation context` will overwrite duplicate fields defined in the transaction `evaluation context`, and fields defined in the invocation `evaluation context` will overwrite duplicate fields defined globally or on the client. Any resulting `evaluation context` from a [before hook](https://openfeature.dev/specification/sections/hooks.md#requirement-434) will overwrite duplicate fields defined globally, on the client, or in the invocation. ```mermaid flowchart LR global("API (global)") transaction("Transaction") client("Client") invocation("Invocation") hook("Before Hooks") global --> transaction transaction --> client client --> invocation invocation --> hook ``` This describes the precedence of all `evaluation context` variants. Depending on the `paradigm`, not all variants might be available in an `SDK` implementation. #### Condition 3.2.4 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 3.2.4.1 > When the global `evaluation context` is set, the `on context changed` function **MUST** run. The SDK implementation must run the `on context changed` function on all registered providers that use the global `evaluation context` whenever it is mutated. ##### Conditional Requirement 3.2.4.2 > When the `evaluation context` for a specific provider is set, the `on context changed` function **MUST** only run on the associated provider. The SDK implementation must run the `on context changed` function only on the provider that is scoped to the mutated `evaluation context`. ### 3.3 Context Propagation [![experimental](https://img.shields.io/static/v1?label=Status\&message=experimental\&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) `Transaction context` is a container for transaction-specific `evaluation context` (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the `transaction context propagator` it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread). The following shows a possible TypeScript implementation using [AsyncLocalStorage (async_hooks)](https://nodejs.org/api/async_context.html): ```typescript export class AsyncLocalStorageTransactionContext implements TransactionContextPropagator { private asyncLocalStorage = new AsyncLocalStorage(); getTransactionContext(): EvaluationContext { return this.asyncLocalStorage.getStore() ?? {}; } setTransactionContext(context: EvaluationContext, callback: () => void): void { this.asyncLocalStorage.run(context, callback); } } /** * This example is based on an express middleware. */ app.use((req: Request, res: Response, next: NextFunction) => { const ip = res.headers.get("X-Forwarded-For") OpenFeature.setTransactionContext({ targetingKey: req.user.id, ipAddress: ip }, () => { // The transaction context is used in any flag evaluation throughout the whole call chain of next next(); }); }) ``` #### Condition 3.3.1 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 3.3.1.1 > The API **SHOULD** have a method for setting a `transaction context propagator`. If there already is a `transaction context propagator`, it is replaced with the new one. #### Condition 3.3.1.2 > The SDK implements context propagation. A language may not have any applicable way of implementing `transaction context propagation` so the language SDK might not implement context propagation. ##### Conditional Requirement 3.3.1.2.1 > The API **MUST** have a method for setting the `evaluation context` of the `transaction context propagator` for the current transaction. If a `transaction context propagator` is set, the SDK will call the [method defined in 3.3.1.3](#conditional-requirement-33122) with this `evaluation context` and so this `evaluation context` will be available during the current transaction. If no `transaction context propagator` is set, this `evaluation context` is not used for evaluations. This method then can be used for example in a request handler to add request-specific information to the `evaluation context`. ##### Conditional Requirement 3.3.1.2.2 > A `transaction context propagator` **MUST** have a method for setting the `evaluation context` of the current transaction. A `transaction context propagator` is responsible for persisting context for the duration of a single transaction. Typically, a transaction context propagator will propagate the context using a language-specific carrier such as [ThreadLocal (Java)](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html), [async hooks (Node.js)](https://nodejs.org/api/async_hooks.html), [Context (Go)](https://pkg.go.dev/context) or another similar mechanism. ##### Conditional Requirement 3.3.1.2.3 > A `transaction context propagator` **MUST** have a method for getting the `evaluation context` of the current transaction. This will be used by the SDK implementation when merging the context for evaluating a feature flag. #### Condition 3.3.2 > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 3.3.2.1 > The API **MUST NOT** have a method for setting a `transaction context propagator`. In the static-context paradigm, context is global, so there must not be different contexts between transactions. --- # 5. Events [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ## Overview `Events` allow consumers (*application integrator*, *application author*, *integration author*) to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. A provider may emit events or run a callback indicating that it received a certain event, optionally providing data associated with that event. Handlers registered on the `client` or the global `API` are then invoked with this data. The data that providers supply in event payloads may include a list of `flag keys` changed, error messages, and possibly updated flag values. ```mermaid graph P(Provider) -->|emit event| A[API] A -->|run handlers| AH("API (global) event handlers") A --> C[Client] C -->|run handlers| CH(Client event handlers) ``` The `domain` of a provider constitutes a logical scope for events. Clients associated with a particular provider through a `domain` run event handlers only when that provider emits events. see: [domain](https://openfeature.dev/specification/glossary.md#domain) ### 5.1. Provider events #### Requirement 5.1.1 > The `feature provider` interface **MUST** define a mechanism for signaling the occurrence of one of a set of events, including `PROVIDER_READY`, `PROVIDER_ERROR`, `PROVIDER_CONFIGURATION_CHANGED`, `PROVIDER_STALE`, `PROVIDER_RECONCILING`, and `PROVIDER_CONTEXT_CHANGED`, with a `provider event details` payload. Providers must emit events to signal all state transitions, including those resulting from lifecycle methods (initialize, reconciliation). The SDK derives provider status from these events. Providers can emit spontaneous events without defining lifecycle methods. Providers that define `initialize` or `on context changed` must support event emission; see [provider status](https://openfeature.dev/specification/sections/providers.md#28-provider-status). If available, native event-emitter or observable/observer language constructs can be used. When a provider is unable to evaluate flags (perhaps due to loss of connection with a remote service) the provider can signal this by emitting a `PROVIDER_ERROR` event. When it recovers, it can emit a `PROVIDER_READY` event. If the error state is irrecoverable, the `PROVIDER_FATAL` error code can be used. If a provider caches rules-sets or previously evaluated flags, and such states cannot be considered up-to-date, the provider can signal this by emitting a `PROVIDER_STALE` event. see: [provider event types](https://openfeature.dev/specification/types.md#provider-events), [`event details`](https://openfeature.dev/specification/types.md#provider-event-details), [events handlers and context reconciliation](#event-handlers-and-context-reconciliation) #### Requirement 5.1.2 > When a `provider` signals the occurrence of a particular `event`, the associated `client` and `API` event handlers **MUST** run. Client event handlers respect the dynamic binding of clients to providers via `domains`. Client event handlers run when the associated provider emits an event. see: [provider event types](https://openfeature.dev/specification/types.md#provider-events) and [event handlers](#52-event-handlers). #### Requirement 5.1.3 > When a `provider` signals the occurrence of a particular `event`, event handlers on clients which are not associated with that provider **MUST NOT** run. Client event handlers respect the dynamic binding of clients to providers via `domains`. Client event handlers do not run when an unassociated provider emits an event. see [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. #### Requirement 5.1.4 > `PROVIDER_ERROR` events **SHOULD** populate the `provider event details`'s `error message` field. The error message field should contain an informative message as to the nature of the error. See [event metadata](https://openfeature.dev/specification/types.md#provider-event-details) #### Requirement 5.1.5 > `PROVIDER_ERROR` events **SHOULD** populate the `provider event details`'s `error code` field. See [event metadata](https://openfeature.dev/specification/types.md#provider-event-details) ### 5.2. Event handlers #### Requirement 5.2.1 > The `client` **MUST** provide a function for associating `handler functions` with a particular `provider event type`. ```java // run the myClientOnReadyHandler function when the PROVIDER_READY event is fired client.addHandler(ProviderEvents.Ready, myClientOnReadyHandler); ``` see: [provider events](#51-provider-events), [`provider event types`](https://openfeature.dev/specification/types.md#provider-events) #### Requirement 5.2.2 > The `API` **MUST** provide a function for associating `handler functions` with a particular `provider event type`. ```java // run the myGlobalErrorHandler function when the PROVIDER_READY event is fired OpenFeature.addHandler(ProviderEvents.Error, myGlobalErrorHandler); ``` see: [provider events](#51-provider-events), [`provider event types`](https://openfeature.dev/specification/types.md#provider-events) #### Requirement 5.2.3 > The `event details` **MUST** contain the `provider name` associated with the event. The `provider name` indicates the provider from which the event originated. This is especially relevant for global event handlers used for general monitoring, such as alerting on provider errors. See [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider), [creating clients](https://openfeature.dev/specification/sections/flag-evaluation.md#creating-clients). #### Requirement 5.2.4 > The `handler function` **MUST** accept an `event details` parameter. see: [`event details`](https://openfeature.dev/specification/types.md#event-details) #### Requirement 5.2.5 > If a `handler function` terminates abnormally, other `handler functions` **MUST** run. #### Requirement 5.2.6 > Event handlers **MUST** persist across `provider` changes. If the underlying provider is changed, existing client and API event handlers will still fire. This means that the order of provider configuration and event handler addition is independent. #### Requirement 5.2.7 > The `API` and `client` **MUST** provide a function allowing the removal of event handlers. ```java // remove an existing handler for a PROVIDER_CONFIGURATION_CHANGED event client.removeHandler(ProviderEvents.ConfigurationChanged, myClientOnChangedHandler); ``` ### Event handlers and initialization The provider signals successful or failed initialization by emitting `PROVIDER_READY` or `PROVIDER_ERROR` events respectively. *Application authors* and *application integrators* use these events to wait for proper initialization of the provider and to do basic monitoring and error handling. #### Requirement 5.3.1 > When the provider emits `PROVIDER_READY`, associated `PROVIDER_READY` handlers **MUST** run. See [provider initialization](https://openfeature.dev/specification/sections/providers.md#24-initialization) and [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider). #### Requirement 5.3.2 > When the provider emits `PROVIDER_ERROR`, associated `PROVIDER_ERROR` handlers **MUST** run. A failed initialization could represent an unrecoverable error, such as bad credentials or a missing file. A failed initialization could also represent a transient error. A provider which maintains a persistent connection to a remote `flag management system` may attempt to reconnect, and emit `PROVIDER_READY` after a failed initialization. See [provider initialization](https://openfeature.dev/specification/sections/providers.md#24-initialization) and [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider). #### Requirement 5.3.3 > Handlers attached after the provider is already in the associated state, **MUST** run immediately. Handlers may be attached at any point in the application lifecycle. Handlers should run immediately if the provider is already in the associated state. For instance, *application authors* may attach readiness handlers to be confident that the system is ready to evaluate flags. If such handlers are attached after the provider underlying the client has already been initialized, they should run immediately. In multi-threaded environments, implementations should ensure that running a handler immediately upon registration does not result in out-of-order event processing (e.g. by holding a lock on the event queue). See [provider initialization](https://openfeature.dev/specification/sections/providers.md#24-initialization), [setting a provider](https://openfeature.dev/specification/sections/flag-evaluation.md#setting-a-provider). ### Event handlers and context reconciliation Providers built to conform to the static context paradigm feature two additional events: `PROVIDER_RECONCILING` and `PROVIDER_CONTEXT_CHANGED`. When the provider is reconciling its internal state (the `on context changed` function is running and not yet terminated), the provider emits `PROVIDER_RECONCILING`. This can be particularly useful for displaying loading indicators while the [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md) is being reconciled. If the `on context changed` function terminates normally, the provider emits `PROVIDER_CONTEXT_CHANGED`; otherwise it emits `PROVIDER_ERROR`. The `PROVIDER_CONTEXT_CHANGED` is used to signal that the associated context has been changed, and flags should be re-evaluated. This can be particularly useful for triggering UI repaints in multiple components when one component updates the [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md). ```mermaid --- title: Provider context reconciliation --- stateDiagram-v2 direction TB READY --> READY:emit(PROVIDER_CONTEXT_CHANGED)* READY --> RECONCILING:emit(PROVIDER_RECONCILING) RECONCILING --> READY:emit(PROVIDER_CONTEXT_CHANGED) RECONCILING --> ERROR:emit(PROVIDER_ERROR) ``` \* Implementations may allow for providers to reconcile synchronously, in which case no `PROVIDER_RECONCILING` event is emitted. #### Condition 5.3.4 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 5.3.4.1 > While the provider's `on context changed` function is executing, associated `RECONCILING` handlers **MUST** run. The provider must emit `PROVIDER_RECONCILING` while it is reconciling its state. In languages with asynchronous semantics, the emission of this event can be skipped if the `on context changed` function of the provider in question executes synchronously. see: [provider event types](https://openfeature.dev/specification/types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](https://openfeature.dev/specification/sections/providers.md#26-provider-context-reconciliation) ##### Conditional Requirement 5.3.4.2 > If the provider's `on context changed` function terminates normally, and no other invocations have yet to terminate, associated `PROVIDER_CONTEXT_CHANGED` handlers **MUST** run. The provider emits `PROVIDER_CONTEXT_CHANGED` upon successful reconciliation; the SDK runs the associated handlers when it receives the event, satisfying this requirement. If `on context changed` is invoked simultaneously or in quick succession, the provider should emit `PROVIDER_CONTEXT_CHANGED` only after the last reentrant invocation terminates normally, to avoid spurious updates from intermediate reconciliations. see: [provider event types](https://openfeature.dev/specification/types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](https://openfeature.dev/specification/sections/providers.md#26-provider-context-reconciliation) ##### Conditional Requirement 5.3.4.3 > If the provider's `on context changed` function terminates abnormally, and no other invocations have yet to terminate, associated `PROVIDER_ERROR` handlers **MUST** run. The provider emits `PROVIDER_ERROR` upon failed reconciliation; the SDK runs the associated handlers when it receives the event, satisfying this requirement. If `on context changed` is invoked simultaneously or in quick succession, the provider should emit `PROVIDER_ERROR` only after the last reentrant invocation terminates abnormally, to avoid spurious updates from intermediate reconciliations. see: [provider event types](https://openfeature.dev/specification/types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](https://openfeature.dev/specification/sections/providers.md#26-provider-context-reconciliation) #### Requirement 5.3.5 > When a provider emits an event, the SDK **MUST** update the `provider status` to the status associated with that event **before** invoking any event handlers for that event, so that handlers observe a consistent status. The SDK derives provider status entirely from events emitted by the provider. The table below summarizes the association between events and provider status: | Event | Associated Status | | -------------------------------- | ------------------------------------------- | | `PROVIDER_READY` | `READY` | | `PROVIDER_STALE` | `STALE` | | `PROVIDER_ERROR` | `ERROR`/`FATAL`\* | | `PROVIDER_CONFIGURATION_CHANGED` | N/A (provider remains in its current state) | | `PROVIDER_CONTEXT_CHANGED` | `READY` | | `PROVIDER_RECONCILING` | `RECONCILING` | \* If the `error code` associated with the error indicates `PROVIDER_FATAL`, the state is set to `FATAL` see: [provider lifecycle management](https://openfeature.dev/specification/sections/flag-evaluation.md#17-provider-lifecycle-management), [provider status](https://openfeature.dev/specification/types.md#provider-status), [error codes](https://openfeature.dev/specification/types.md#error-code) --- # 1. Flag Evaluation API [![stable](https://img.shields.io/static/v1?label=Status\&message=stable\&color=green)](https://github.com/open-feature/spec/tree/main/specification#stable) ## Overview The `evaluation API` allows for the evaluation of feature flag values, independent of any flag control plane or vendor. In the absence of a [provider](https://openfeature.dev/specification/sections/providers.md) the `evaluation API` uses the "No-op provider", which simply returns the supplied default flag value. ### 1.1. API Initialization and Configuration #### Requirement 1.1.1 > The `API`, and any state it maintains **SHOULD** exist as a global singleton, even in cases wherein multiple versions of the `API` are present at runtime. It's important that multiple instances of the `API` not be active, so that state stored therein, such as the registered `provider`, static global `evaluation context`, and globally configured `hooks` allow the `API` to behave predictably. This can be difficult in some runtimes or languages, but implementors should make their best effort to ensure that only a single instance of the `API` is used. ### Setting a provider #### Requirement 1.1.2.1 > The `API` **MUST** define a `provider mutator`, a function to set the default `provider`, which accepts an API-conformant `provider` implementation. ```typescript // example provider mutator OpenFeature.setProvider(new MyProvider()); ``` The example above sets the default provider. This provider is used if a client is not bound to a specific provider via a [domain](https://openfeature.dev/specification/glossary.md#domain). See [provider](https://openfeature.dev/specification/sections/providers.md), [creating clients](#creating-clients) for details. #### Requirement 1.1.2.2 > The `provider mutator` function **MUST** invoke the `initialize` function on the newly registered provider before using it to resolve flag values, supplying the bound `domain`, if any. Application authors can await the newly set `provider's` readiness using the `PROVIDER_READY` event. Provider instances which are already active (because they have been bound to another `domain` or otherwise) need not be initialized again. The `provider's` readiness state can be determined from its `status` member/accessor. See [event handlers and initialization](https://openfeature.dev/specification/sections/events.md#event-handlers-and-initialization), [provider initialization](https://openfeature.dev/specification/sections/providers.md#24-initialization), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. #### Requirement 1.1.2.3 > The `provider mutator` function **MUST** invoke the `shutdown` function on the previously registered provider once it's no longer being used to resolve flag values. When a provider is no longer in use, it should be disposed of using its `shutdown` mechanism. Provider instances which are bound to multiple `domains` won't be shut down until the last binding is removed. see [shutdown](https://openfeature.dev/specification/sections/providers.md#25-shutdown), [setting a provider](#setting-a-provider), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. #### Requirement 1.1.2.4 > The `API` **SHOULD** provide functions to set a provider and wait for `initialize` to terminate and its resulting lifecycle event to be processed. This function not only sets the provider, but ensures that the provider is ready (or in error) before returning or settling. The SDK waits for both `initialize` termination and the status update from the resulting `PROVIDER_READY` or `PROVIDER_ERROR` event (see [provider events](https://openfeature.dev/specification/sections/events.md#51-provider-events)) before settling. Application event handlers need not complete before the function returns or settles. ```java // default provider OpenFeatureAPI.getInstance().setProviderAndWait(myprovider); // this method blocks until the provider is ready or in error // client uses the default provider Client client = OpenFeatureAPI.getInstance().getClient(); // provider associated with domain-1 OpenFeatureAPI.getInstance().setProviderAndWait('domain-1', myprovider); // this method blocks until the provider is ready or in error // client uses provider associated with the domain named 'domain-1' Client client = OpenFeatureAPI.getInstance().getClient('domain-1'); ``` Though it's possible to use [events](https://openfeature.dev/specification/sections/events.md) to await provider readiness, such functions can make things simpler for `application authors` and `integrators`. Implementations indicate an error in a manner idiomatic to the language in use (returning an error, throwing an exception, etc). #### Requirement 1.1.3 > The `API` **MUST** provide a function to bind a given `provider` to one or more clients using a `domain`. If the domain already has a bound provider, it is overwritten with the new mapping. ```java OpenFeature.setProvider("domain-1", new MyProvider()); ``` Clients can be associated with a particular provider by supplying a matching `domain` when the provider is set. See [creating clients](#creating-clients), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. #### Requirement 1.1.4 > The `API` **MUST** provide a function to add `hooks` which accepts one or more API-conformant `hooks`, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed. ```typescript // example hook attachment OpenFeature.addHooks([new MyHook()]); ``` See [hooks](https://openfeature.dev/specification/sections/hooks.md) for details. #### Requirement 1.1.5 > The `API` **MUST** provide a function for retrieving the metadata field of the configured `provider`. ```typescript // example provider accessor OpenFeature.getProviderMetadata(); ``` It's possible to access provider metadata using a `domain`. If a provider has not been registered under the requested domain, the default provider metadata is returned. ```typescript // example provider accessor OpenFeature.getProviderMetadata("domain-1"); ``` See [provider](https://openfeature.dev/specification/sections/providers.md), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. ### Creating clients #### Requirement 1.1.6 > The `API` **MUST** provide a function for creating a `client` which accepts the following options: > > * domain (optional): A logical string identifier for binding a client to a provider. ```java // example client creation and retrieval OpenFeature.getClient(); ``` It's possible to create a client that is associated with a `domain`. The client will use a provider in the same `domain` if one exists, otherwise, the default provider is used. ```java // example client creation and retrieval using a domain OpenFeature.getClient("domain-1"); ``` See [setting a provider](#setting-a-provider), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. #### Requirement 1.1.7 > The client creation function **MUST NOT** throw, or otherwise abnormally terminate. Clients may be created in critical code paths, and even per-request in server-side HTTP contexts. Therefore, in keeping with the principle that OpenFeature should never cause abnormal execution of the first party application, this function should never throw. Abnormal execution in initialization should instead occur during provider registration. #### Condition 1.1.8 > The `provider` declares that it is `domain-scoped`. see: [Requirement 2.4.3](https://openfeature.dev/specification/sections/providers.md#requirement-243) ##### Conditional Requirement 1.1.8.1 > The `provider mutator` **MUST NOT** bind a `domain-scoped` provider instance to more than one `domain`, rejecting any attempt to bind an already-bound instance to an additional `domain`. A `domain-scoped` provider keys per-`domain` state on the single `domain` supplied to its `initialize` function. Rejection should occur in a manner idiomatic to the implementation language (throwing, returning an error, etc.), and leaves any existing binding intact. ### 1.2. Client Usage #### Requirement 1.2.1 > The client **MUST** provide a method to add `hooks` which accepts one or more API-conformant `hooks`, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed. ```typescript // example hook attachment client.addHooks([new MyHook()]); ``` See [hooks](https://openfeature.dev/specification/sections/hooks.md) for details. #### Requirement 1.2.2 > The client interface **MUST** define a `metadata` member or accessor, containing an immutable `domain` field or accessor of type string, which corresponds to the `domain` value supplied during client creation. ```typescript client.getMetadata().getDomain(); // "domain-1" ``` In previous drafts, this property was called `name`. For backwards compatibility, implementations should consider `name` an alias to `domain`. ### 1.3. Flag Evaluation [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) #### Condition 1.3.1 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 1.3.1.1 > The `client` **MUST** provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), `evaluation context` (optional), and `evaluation options` (optional), which returns the flag value. ```typescript // example boolean flag evaluation boolean myBool = client.getBooleanValue('bool-flag', false); // example overloaded string flag evaluation with optional params string myString = client.getStringValue('string-flag', 'N/A', evaluationContext, options); // example number flag evaluation number myNumber = client.getNumberValue('number-flag', 75); // example overloaded structure flag evaluation with optional params MyStruct myStruct = client.getObjectValue('structured-flag', { text: 'N/A', percentage: 75 }, evaluationContext, options); ``` See [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md) for details. #### Condition 1.3.2 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 1.3.2.1 > The `client` **MUST** provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), and `evaluation options` (optional), which returns the flag value. ```typescript // example boolean flag evaluation boolean myBool = client.getBooleanValue('bool-flag', false); // example overloaded string flag evaluation with optional params string myString = client.getStringValue('string-flag', 'N/A', options); // example number flag evaluation number myNumber = client.getNumberValue('number-flag', 75); // example overloaded structure flag evaluation with optional params MyStruct myStruct = client.getObjectValue('structured-flag', { text: 'N/A', percentage: 75 }, options); ``` #### Condition 1.3.3 > The implementation language differentiates between floating-point numbers and integers. ##### Conditional Requirement 1.3.3.1 > The client **SHOULD** provide functions for floating-point numbers and integers, consistent with language idioms. ```java int getIntValue(String flag, int defaultValue); long getLongValue(String flag, long defaultValue); double getDoubleValue(String flag, double defaultValue); ``` See [types](https://openfeature.dev/specification/types.md) for details. #### Requirement 1.3.4 > The `client` **SHOULD** guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied `default value` should be returned. ### 1.4. Detailed Flag Evaluation [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) The *detailed evaluation* functions behave similarly to the standard flag evaluation functions, but provide additional metadata useful for telemetry, troubleshooting, debugging, and advanced integrations. Note Metadata included in the `evaluation details` should be considered "best effort", since not all providers will be able to provide these details (such as the reason a flag resolved to a particular value). see: [evaluation details](https://openfeature.dev/specification/types.md#evaluation-details) type #### Condition 1.4.1 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 1.4.1.1 > The `client` **MUST** provide methods for detailed flag value evaluation with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), `evaluation context` (optional), and `evaluation options` (optional), which returns an `evaluation details` structure. ```typescript // example detailed boolean flag evaluation FlagEvaluationDetails myBoolDetails = client.getBooleanDetails('bool-flag', false); // example detailed string flag evaluation FlagEvaluationDetails myStringDetails = client.getStringDetails('string-flag', 'N/A', evaluationContext, options); // example detailed number flag evaluation FlagEvaluationDetails myNumberDetails = client.getNumberDetails('number-flag', 75); // example detailed structure flag evaluation FlagEvaluationDetails myStructDetails = client.getObjectDetails('structured-flag', { text: 'N/A', percentage: 75 }, evaluationContext, options); ``` #### Condition 1.4.2 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 1.4.2.1 > The `client` **MUST** provide methods for detailed flag value evaluation with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), and `evaluation options` (optional), which returns an `evaluation details` structure. ```typescript // example detailed boolean flag evaluation FlagEvaluationDetails myBoolDetails = client.getBooleanDetails('bool-flag', false); // example detailed string flag evaluation FlagEvaluationDetails myStringDetails = client.getStringDetails('string-flag', 'N/A', options); // example detailed number flag evaluation FlagEvaluationDetails myNumberDetails = client.getNumberDetails('number-flag', 75); // example detailed structure flag evaluation FlagEvaluationDetails myStructDetails = client.getObjectDetails('structured-flag', { text: 'N/A', percentage: 75 }, options); ``` #### Requirement 1.4.3 > The `evaluation details` structure's `value` field **MUST** contain the evaluated flag value. #### Condition 1.4.4 > The language supports generics (or an equivalent feature). ##### Conditional Requirement 1.4.4.1 > The `evaluation details` structure **SHOULD** accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field. #### Requirement 1.4.5 > The `evaluation details` structure's `flag key` field **MUST** contain the `flag key` argument passed to the detailed flag evaluation method. #### Requirement 1.4.6 > In cases of normal execution, the `evaluation details` structure's `variant` field **MUST** contain the value of the `variant` field in the `flag resolution` structure returned by the configured `provider`, if the field is set. #### Requirement 1.4.7 > In cases of normal execution, the `evaluation details` structure's `reason` field **MUST** contain the value of the `reason` field in the `flag resolution` structure returned by the configured `provider`, if the field is set. #### Requirement 1.4.8 > In cases of abnormal execution, the `evaluation details` structure's `error code` field **MUST** contain an `error code`. See [error code](https://openfeature.dev/specification/types.md#error-code) for details. #### Requirement 1.4.9 > In cases of abnormal execution (network failure, unhandled error, etc) the `reason` field in the `evaluation details` **SHOULD** indicate an error. #### Requirement 1.4.10 > Methods, functions, or operations on the client **MUST NOT** throw exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the `default value` in the event of abnormal execution. Exceptions include functions or methods for the purposes for configuration or setup. Configuration code includes code to set the provider, instantiate providers, and configure the global API object. #### Requirement 1.4.11 > Methods, functions, or operations on the client **SHOULD NOT** write log messages. The client methods (particularly the evaluation methods) run in hot code paths. Logging (even at error level) can cause a huge volume of log entries. For example, in a circumstance in which an application expecting a particular flag to exist is deployed in advance of that flag's being defined in the management system, logs can become inundated with `FLAG_NOT_FOUND` messages and related stack traces. Logging in these code paths is highly discouraged. Application authors can attach a [logging hook](https://openfeature.dev/specification/appendix-a.md#logging-hook) or author their own custom logging hook(s) to help with debugging or satisfy their particular logging needs. Logging is encouraged in functions to do with configuration, initialization, shutdown, etc. #### Requirement 1.4.12 > The `client` **SHOULD** provide asynchronous or non-blocking mechanisms for flag evaluation. It's recommended to provide non-blocking mechanisms for flag evaluation, particularly in languages or environments wherein there's a single thread of execution. #### Requirement 1.4.13 > In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional details about the nature of the error. #### Requirement 1.4.14 > If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field **MUST** contain that value. Otherwise, it **MUST** contain an empty record. This `flag metadata` field is intended as a mechanism for providers to surface additional information about a feature flag (or its evaluation) beyond what is defined within the OpenFeature spec itself. The primary consumer of this information is a provider-specific hook. #### Condition 1.4.15 > The implementation language supports a mechanism for marking data as immutable. ##### Conditional Requirement 1.4.15.1 > Condition: `Flag metadata` **MUST** be immutable. ### Evaluation Options #### Requirement 1.5.1 > The `evaluation options` structure's `hooks` field denotes an ordered collection of hooks that the client **MUST** execute for the respective flag evaluation, in addition to those already configured. See [hooks](https://openfeature.dev/specification/sections/hooks.md) for details. ### 1.6. Shutdown [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) The API's `shutdown` function defines a means of graceful shutdown, calling the `shutdown` function on all providers, allowing them to flush telemetry, clean up connections, and release any relevant resources. It also provides a means of resetting the API object to its default state, removing all hooks, event handlers, evaluation context, transaction context propagators, providers, and setting a "No-op provider"; this is useful for testing purposes. It's recommended that application-authors call this function on application shutdown, and after the completion of test suites which make use of the SDK. #### Requirement 1.6.1 > The API **MUST** define a function to propagate a shutdown request to all providers. The global API object defines a `shutdown` function, which will call the respective `shutdown` function on all providers. Alternatively, implementations might leverage language idioms such as auto-disposable interfaces or some means of cancellation signal propagation to allow for graceful shutdown. This shutdown function unconditionally calls the shutdown function on all registered providers, regardless of their state. see: [`shutdown`](https://openfeature.dev/specification/sections/providers.md#25-shutdown) #### Requirement 1.6.2 > The API's `shutdown` function **MUST** reset all state of the API, removing all hooks, event handlers, evaluation context, transaction context propagators, and providers. After shutting down all providers, the `shutdown` function resets the state of the API. This is especially useful for testing purposes to restore the API to a known state. ### 1.7. Provider Lifecycle Management The implementation maintains an internal representation of the state of configured providers, tracking the lifecycle of each provider. This state of the provider is exposed on associated `clients`. The diagram below illustrates the possible states and transitions of the `state` field for a provider during the provider lifecycle. ```mermaid --- title: Provider lifecycle --- stateDiagram-v2 direction LR [*] --> NOT_READY NOT_READY --> READY:initialize() NOT_READY --> ERROR:initialize() NOT_READY --> FATAL:initialize() FATAL --> [*] READY --> ERROR:#ast; ERROR --> READY:#ast; READY --> STALE:#ast; STALE --> READY:#ast; STALE --> ERROR:#ast; READY --> NOT_READY:shutdown() STALE --> NOT_READY:shutdown() ERROR --> NOT_READY:shutdown() READY --> RECONCILING:::client:setContext() RECONCILING:::client --> READY RECONCILING:::client --> ERROR classDef client fill:#888 ``` \* transitions occurring when associated events are spontaneously emitted from the provider █ only defined in static-context (client-side) paradigm Note Only SDKs implementing the [static context (client-side) paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) define `RECONCILING` to facilitate [context reconciliation](https://openfeature.dev/specification/sections/providers.md#26-provider-context-reconciliation). #### Requirement 1.7.1 > The `client` **MUST** define a `provider status` accessor which indicates the readiness of the associated provider, with possible values `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`. The SDK derives provider status from events emitted by the provider and keeps it up-to-date as events are received. see [provider status](https://openfeature.dev/specification/types.md#provider-status), [provider events](https://openfeature.dev/specification/sections/events.md#51-provider-events) #### Condition 1.7.2 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 1.7.2.1 > In addition to `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`, the `provider status` accessor **MUST** support possible value `RECONCILING`. In the static context paradigm, the implementation must define a `provider status` indicating that a provider is reconciling its internal state due to a context change. #### Requirement 1.7.3 > The client's `provider status` accessor **MUST** indicate `READY` after the provider emits `PROVIDER_READY` following initialization. see: [provider status requirements](https://openfeature.dev/specification/sections/providers.md#28-provider-status), [event-status mapping](https://openfeature.dev/specification/sections/events.md#requirement-535) #### Requirement 1.7.4 > The client's `provider status` accessor **MUST** indicate `ERROR` after the provider emits `PROVIDER_ERROR` following initialization. see: [provider status requirements](https://openfeature.dev/specification/sections/providers.md#28-provider-status), [event-status mapping](https://openfeature.dev/specification/sections/events.md#requirement-535) #### Requirement 1.7.5 > The client's `provider status` accessor **MUST** indicate `FATAL` after the provider emits `PROVIDER_ERROR` with error code `PROVIDER_FATAL` following initialization. see: [provider status requirements](https://openfeature.dev/specification/sections/providers.md#28-provider-status), [event-status mapping](https://openfeature.dev/specification/sections/events.md#requirement-535) #### Requirement 1.7.6 > The client's `provider status` accessor **MUST** indicate `NOT_READY` once the `shutdown` function of the associated provider terminates. Regardless of the success of the provider's `shutdown` function, the `provider status` should convey the provider is no longer ready to use once the shutdown function terminates. Unlike other status transitions, this shutdown is inferred by the SDK; because the SDK initiates the shutdown call, no event from the provider is required. ### 1.8. Isolated API Instances [![experimental](https://img.shields.io/static/v1?label=Status\&message=experimental\&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) While the `API` [functions as a global singleton](#requirement-111) in the default case, certain use cases require independent instances of the `API` with fully isolated state. Examples include micro-frontend architectures (where separately developed applications coexist in a single runtime), dependency injection frameworks and IoC containers (which manage object lifecycles outside the singleton's scope), testing scenarios (where parallel tests mutating shared state can interfere with each other), and server-side applications composed of multiple submodules each requiring distinct providers. #### Requirement 1.8.1 > The `API` **MUST** expose a factory function which creates and returns a new, independent instance of the `API`. Each instance returned by this factory function maintains its own state, including providers, `evaluation context`, hooks, event handlers, and transaction context propagators. Instances created by the factory function do not share state with the "default" global singleton or with each other. ```java // example factory function OpenFeatureAPI isolated = createIsolatedOpenFeatureAPI(); isolated.setProvider(new MyProvider()); Client client = isolated.getClient(); ``` See [application integrator](https://openfeature.dev/specification/glossary.md#application-integrator), [isolated API instance](https://openfeature.dev/specification/glossary.md#isolated-api-instance) for details. #### Requirement 1.8.2 > Instances returned by the factory function **MUST** conform to the same `API` contract as the global singleton, including flag evaluation, provider management, context, hooks, events, and shutdown functionality. An isolated `API` instance is functionally equivalent to the global singleton, but with independent state. Application code which uses a `client` obtained from an isolated instance behaves identically to code using a `client` from the global singleton. #### Requirement 1.8.3 > The factory function for creating isolated instances **SHOULD** be housed in a distinct module, import path, package, or namespace from the global singleton `API`. The factory function should be intentionally less discoverable than the default singleton, reducing the risk of `application authors` inadvertently creating isolated instances when the singleton would be more appropriate. The distinct import path serves as an explicit signal that the consumer is opting into advanced, non-default behavior. ```typescript // example: the factory function is accessed from a distinct module import { createIsolatedOpenFeatureAPI } from '@openfeature/web-sdk/isolated'; ``` #### Requirement 1.8.4 > A `provider` instance **SHOULD NOT** be registered with more than one `API` instance simultaneously. Because the `API` instance manages the [lifecycle](https://openfeature.dev/specification/sections/providers.md) of its associated providers (including initialization, shutdown, and event handling), binding a `provider` to more than one `API` instance could result in undefined behavior. A `provider` instance can be registered with multiple `domains` within a single `API` instance, unless it declares itself `domain-scoped` (see [Requirement 2.4.3](https://openfeature.dev/specification/sections/providers.md#requirement-243)), in which case it can be bound to at most one `domain`. When a provider is no longer associated with an `API` instance, it can be registered to another. See [setting a provider](#setting-a-provider), [domain](https://openfeature.dev/specification/glossary.md#domain) for details. --- # 4. Hooks [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ## Overview `Hooks` are a mechanism whereby application developers can add arbitrary behavior to flag evaluation. They operate similarly to middleware in many web frameworks. Hooks add their logic at any of four specific stages of flag evaluation: * `before`, immediately before flag evaluation * `after`, immediately after successful flag evaluation * `error`, immediately after an unsuccessful flag evaluation * `finally`, unconditionally after flag evaluation ```mermaid flowchart LR B(('Before' stage)) ---> FE[Flag Evaluation] B -..->|Error| E FE ---> A(('After' stage)) FE -..->|Error| E(('Error' stage)) A ---> F(('Finally' stage)) E -..-> F ``` Hooks can be configured to run globally (impacting all flag evaluations), per client, or per flag evaluation invocation. Some example use cases for a hook include adding additional data to the [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md), performing validation on the received flag value, providing data to telemetric tools, and logging errors. ### Definitions **Hook**: Application author/integrator-supplied logic that is called by the OpenFeature framework at a specific stage. **Stage**: An explicit portion of the flag evaluation lifecycle. e.g. `before` being "before the [resolution](https://openfeature.dev/specification/glossary.md#resolving-flag-values) is run. **Invocation**: A single call to evaluate a flag. `client.getBooleanValue(..)` is an invocation. **API**: The global API singleton. ### 4.1. Hook context Hook context exists to provide hooks with information about the invocation and propagate data between hook stages. #### Requirement 4.1.1 > Hook context **MUST** provide: the `flag key`, `flag value type`, `evaluation context`, `default value`, and `hook data`. The `evaluation context` provided in the hook context refers to the **merged evaluation context** as specified in [Requirement 3.2.3](https://openfeature.dev/specification/sections/evaluation-context.md#requirement-323). #### Requirement 4.1.2 > The `hook context` **SHOULD** provide access to the `client metadata` and the `provider metadata` fields. #### Requirement 4.1.3 > The `flag key`, `flag type`, and `default value` properties **MUST** be immutable. If the language does not support immutability, the hook **MUST NOT** modify these properties. #### Condition 4.1.4 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 4.1.4.1 > The evaluation context **MUST** be mutable only within the `before` hook. #### Requirement 4.1.5 > The `hook data` **MUST** be mutable. Either the `hook data` reference itself must be mutable, or it must allow mutation of its contents. Mutable reference: ```js hookContext.hookData = {'my-key': 'my-value'} ``` Mutable content: ```js hookContext.hookData.set('my-key', 'my-value') ``` ### 4.2. Hook Hints #### Requirement 4.2.1 > `hook hints` **MUST** be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. #### Condition 4.2.2 > The implementation language supports a mechanism for marking data as immutable. ##### Conditional Requirement 4.2.2.1 > Condition: `Hook hints` **MUST** be immutable. ##### Conditional Requirement 4.2.2.2 > Condition: The client `metadata` field in the `hook context` **MUST** be immutable. ##### Conditional Requirement 4.2.2.3 > Condition: The provider `metadata` field in the `hook context` **MUST** be immutable. ### 4.3. Hook creation and parameters #### Requirement 4.3.1 > Hooks **MUST** specify at least one stage. #### Requirement 4.3.2 > `Hook data` **MUST** must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. The hook data is not shared between different hooks. Example showing data between `before` and `after` stage for two different hooks. ```mermaid sequenceDiagram actor Application participant Client participant HookA participant HookB Application->>Client: getBooleanValue('my-bool', myContext, false) activate Client Client-->>Client: create hook data for HookA Client->>HookA: before(hookContext: {data: {}, ... }) activate HookA HookA-->>HookA: hookContext.hookData.set('key',' data for A') HookA-->>Client: (return) deactivate HookA Client-->>Client: create hook data for HookB Client->>HookB: before(hookContext: {data: {}, ... }, hints) activate HookB HookB-->>HookB: hookContext.hookData.set('key', 'data for B') deactivate HookB Client-->>Client: Flag evaluation Client->>HookB: after(hookContext: {data: {key: 'data for B'}, ... }, detail, hints) activate HookB HookB-->>Client: (return) deactivate HookB Client->>HookA: after(hookContext: {data: {'key': 'data for A'}, ... }) activate HookA HookA-->>Client: (return) deactivate HookA Client-->>Application: true deactivate Client ``` #### Condition 4.3.2 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 4.3.2.1 > The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters and returns either an `evaluation context` or nothing. ```java EvaluationContext | void before(HookContext hookContext, HookHints hints); ``` #### Condition 4.3.3 [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 4.3.3.1 > The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters. It has no return value. ```java void before(HookContext hookContext, HookHints hints); ``` #### Requirement 4.3.4 > Any `evaluation context` returned from a `before` hook **MUST** be passed to subsequent `before` hooks (via `HookContext`). #### Requirement 4.3.5 > When `before` hooks have finished executing, any resulting `evaluation context` **MUST** be merged with the existing `evaluation context`. Evaluation context merge order is defined in [Context levels and merging](https://openfeature.dev/specification/sections/evaluation-context.md#32-context-levels-and-merging). #### Requirement 4.3.6 > The `after` stage **MUST** run after flag resolution occurs. It accepts a `hook context` (required), `evaluation details` (required) and `hook hints` (optional). It has no return value. #### Requirement 4.3.7 > The `error` hook **MUST** run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), and `hook hints` (optional). It has no return value. #### Requirement 4.3.8 > The `finally` hook **MUST** run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required), `evaluation details` (required) and `hook hints` (optional). It has no return value. The evaluation details passed to the `finally` stage matches the evaluation details returned to the application author. #### Condition 4.3.9 > `finally` is a reserved word in the language. ##### Conditional Requirement 4.3.9.1 > Instead of `finally`, `finallyAfter` **SHOULD** be used. ### 4.4. Hook registration & ordering #### Requirement 4.4.1 > The API, Client, Provider, and invocation **MUST** have a method for registering hooks. ```js OpenFeature.addHooks(new Hook1()); //... Client client = OpenFeature.getClient(); client.addHooks(new Hook2()); ` //... client.getValue('my-flag', 'defaultValue', new Hook3()); ``` #### Requirement 4.4.2 > Hooks **MUST** be executed "stack-wise" with respect to flag resolution, prioritizing increasing specificity (API, Client, Invocation, Provider) first, and the order in which they were added second. Before flag resolution (the `before` stage), hooks run in the order `API` -> `Client` -> `Invocation` -> `Provider`, and within those, in the order in which they were added. After flag evaluation (the `after`, `error`, or `finally` stages), hooks run in the order `Provider` -> `Invocation` -> `Client` -> `API`, and within those, in reverse of the order in which they were added. This achieves intuitive "stack-like" or "LIFO" behavior for side effects and transformations. Given hooks A - H, each implementing the both the `before` and `after` stages, added at the following levels and order: * API: \[A, B] * Client: \[C, D] * Invocation: \[E, F] * Provider: \[G, H] The expected order of execution is: ```mermaid flowchart TB subgraph FlagResolution [Flag Resolution] flagResolution[flagResolution] end subgraph Provider [Provider Layer] G_before[G.before] H_before[H.before] G_after[G.after] H_after[H.after] end subgraph Invocation [Invocation Layer] E_before[E.before] F_before[F.before] E_after[E.after] F_after[F.after] end subgraph Client [Client Layer] C_before[C.before] D_before[D.before] C_after[C.after] D_after[D.after] end subgraph API [API Layer] A_before[A.before] B_before[B.before] A_after[A.after] B_after[B.after] end A_before --> B_before B_before --> C_before C_before --> D_before D_before --> E_before E_before --> F_before F_before --> G_before G_before --> H_before H_before --> flagResolution flagResolution --> H_after H_after --> G_after G_after --> F_after F_after --> E_after E_after --> D_after D_after --> C_after C_after --> B_after B_after --> A_after ``` #### Requirement 4.4.3 > If a `finally` hook abnormally terminates, evaluation **MUST** proceed, including the execution of any remaining `finally` hooks. In languages with try/catch semantics, this means that exceptions thrown in `finally` hooks should be caught and not propagated up the call stack. #### Requirement 4.4.4 > If an `error` hook abnormally terminates, evaluation **MUST** proceed, including the execution of any remaining `error` hooks. In languages with try/catch semantics, this means that exceptions thrown in `error` hooks should be caught, and not propagated up the call stack. #### Requirement 4.4.5 > If an error occurs in the `before` or `after` hooks, the `error` hooks **MUST** be invoked. #### Requirement 4.4.6 > If an error occurs during the evaluation of `before` or `after` hooks, any remaining hooks in the `before` or `after` stages **MUST NOT** be invoked. #### Requirement 4.4.7 > If an error occurs in the `before` hooks, the default value **MUST** be returned. Before hooks can impact evaluation by various means, such as mutating the `evaluation context`. Therefore, an error in the `before` hooks is considered abnormal execution, and the default should be returned. ### [Flag evaluation options](https://openfeature.dev/specification/types.md#evaluation-options) Usage might look something like: ```python val = client.get_boolean_value('my-key', False, evaluation_options={ 'hooks': new MyHook(), 'hook_hints': {'side-item': 'onion rings'} }) ``` see: [Flag evaluation options](https://openfeature.dev/specification/sections/flag-evaluation.md#evaluation-options) #### Requirement 4.5.1 > `Flag evaluation options` **MAY** contain `hook hints`, a map of data to be provided to hook invocations. #### Requirement 4.5.2 > `hook hints` **MUST** be passed to each hook. #### Requirement 4.5.3 > The hook **MUST NOT** alter the `hook hints` structure. ### 4.6. Hook data Hook data exists to allow hook stages to share data for a specific evaluation. For instance a span for OpenTelemetry could be created in a `before` stage and closed in an `after` stage. Hook data is scoped to a specific hook instance. The different stages of a hook share the same data, but different hooks have different hook data instances. ```java public Optional before(HookContext context, HookHints hints) { SpanBuilder builder = tracer.spanBuilder('sample') .setParent(Context.current().with(Span.current())); Span span = builder.startSpan() context.hookData.set("span", span); } public void after(HookContext context, FlagEvaluationDetails details, HookHints hints) { // Only accessible by this hook for this specific evaluation. Object value = context.hookData.get("span"); if (value instanceof Span) { Span span = (Span) value; span.end(); } } ``` #### Requirement 4.6.1 > `hook data` **MUST** be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of any type. Access to hook data is restricted to only a single hook instance, and it has no serialization requirements, and as a result does not require any value type restrictions. Example TypeScript definition: ```js type HookData = Record; ``` --- # 2. Provider [![stable](https://img.shields.io/static/v1?label=Status\&message=stable\&color=green)](https://github.com/open-feature/spec/tree/main/specification#stable) ## Overview The `provider` API defines interfaces that Provider Authors can use to abstract a particular flag management system, thus enabling the use of the `evaluation API` by Application Authors. Providers are the "translator" between the flag evaluation calls made in application code, and the flag management system that stores flags and in some cases evaluates flags. At a minimum, providers should implement some basic evaluation methods which return flag values of the expected type. In addition, providers may transform the [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md) appropriately in order to be used in dynamic evaluation of their associated flag management system, provide insight into why evaluation proceeded the way it did, and expose configuration options for their associated flag management system. Hypothetical provider implementations might wrap a vendor SDK, embed an REST client, or read flags from a local file. ```mermaid flowchart LR A[Application] --> API(Evaluation API) API --> P[Provider] P --> FMS[(Flag Management System)] ``` ### 2.1. Feature Provider Interface #### Requirement 2.1.1 > The provider interface **MUST** define a `metadata` member or accessor, containing a `name` field or accessor of type string, which identifies the provider implementation. ```typescript provider.getMetadata().getName(); // "my-custom-provider" ``` ### 2.2 Flag Value Resolution `Providers` are implementations of the `feature provider` interface, which may wrap vendor SDKs, REST API clients, or otherwise resolve flag values from the runtime environment. #### Requirement 2.2.1 > The `feature provider` interface **MUST** define methods to resolve flag values, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required) and `evaluation context` (optional), which returns a `resolution details` structure. ```typescript // example flag resolution function resolveBooleanValue(flagKey, defaultValue, context); ``` see: [flag resolution structure](https://openfeature.dev/specification/types.md#resolution-details), [flag value resolution](https://openfeature.dev/specification/glossary.md#resolving-flag-values) #### Condition 2.2.2 > The implementing language type system differentiates between strings, numbers, booleans and structures. ##### Conditional Requirement 2.2.2.1 > The `feature provider` interface **MUST** define methods for typed flag resolution, including boolean, numeric, string, and structure. ```typescript // example boolean flag value resolution ResolutionDetails resolveBooleanValue(string flagKey, boolean defaultValue, context: EvaluationContext); // example string flag value resolution ResolutionDetails resolveStringValue(string flagKey, string defaultValue, context: EvaluationContext); // example number flag value resolution ResolutionDetails resolveNumberValue(string flagKey, number defaultValue, context: EvaluationContext); // example structure flag value resolution ResolutionDetails resolveStructureValue(string flagKey, JsonObject defaultValue, context: EvaluationContext); ``` #### Requirement 2.2.3 > In cases of normal execution, the `provider` **MUST** populate the `resolution details` structure's `value` field with the resolved flag value. #### Requirement 2.2.4 > In cases of normal execution, the `provider` **SHOULD** populate the `resolution details` structure's `variant` field with a string identifier corresponding to the returned flag value. For example, the flag value might be `3.14159265359`, and the variant field's value might be `"pi"`. The value of the variant field might only be meaningful in the context of the flag management system associated with the provider. For example, the variant may be a UUID corresponding to the variant in the flag management system, or an index corresponding to the variant in the flag management system. #### Requirement 2.2.5 > The `provider` **SHOULD** populate the `resolution details` structure's `reason` field with `"STATIC"`, `"DEFAULT",` `"TARGETING_MATCH"`, `"SPLIT"`, `"CACHED"`, `"DISABLED"`, `"UNKNOWN"`, `"STALE"`, `"ERROR"` or some other string indicating the semantic reason for the returned flag value. As indicated in the definition of the [`resolution details`](https://openfeature.dev/specification/types.md#resolution-details) structure, the `reason` should be a string. This allows providers to reflect accurately why a flag was resolved to a particular value. #### Requirement 2.2.6 > In cases of normal execution, the `provider` **MUST NOT** populate the `resolution details` structure's `error code` field, or otherwise must populate it with a null or falsy value. #### Requirement 2.2.7 > In cases of abnormal execution, the `provider` **MUST** indicate an error using the idioms of the implementation language, with an associated `error code` and optional associated `error message`. The provider might throw an exception, return an error, or populate the `error code` object on the returned `resolution details` structure to indicate a problem during flag value resolution. This includes situations where the provider is not yet initialized or has encountered an irrecoverable error; in such cases, the provider indicates the error (e.g. with error codes `PROVIDER_NOT_READY` or `PROVIDER_FATAL`), and the client returns the default value per Requirement 1.4.10. See [error code](https://openfeature.dev/specification/types.md#error-code) for details. ```typescript // example throwing an exception with an error code and optional error message. throw new ProviderError(ErrorCode.INVALID_CONTEXT, "The 'foo' attribute must be a string."); ``` #### Condition 2.2.8 > The implementation language supports generics (or an equivalent feature). ##### Conditional Requirement 2.2.8.1 > The `resolution details` structure **SHOULD** accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field. ```typescript // example boolean flag value resolution with generic argument ResolutionDetails resolveBooleanValue(string flagKey, boolean defaultValue, context: EvaluationContext); // example string flag value resolution with generic argument ResolutionDetails resolveStringValue(string flagKey, string defaultValue, context: EvaluationContext); // example number flag value resolution with generic argument ResolutionDetails resolveNumberValue(string flagKey, number defaultValue, context: EvaluationContext); // example structure flag value resolution with generic argument ResolutionDetails resolveStructureValue(string flagKey, MyStruct defaultValue, context: EvaluationContext); ``` #### Requirement 2.2.9 > The `provider` **SHOULD** populate the `resolution details` structure's `flag metadata` field. #### Requirement 2.2.10 > `flag metadata` **MUST** be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number`. ### 2.3. Provider hooks A `provider hook` exposes a mechanism for `provider authors` to register [`hooks`](https://openfeature.dev/specification/sections/hooks.md) to tap into various stages of the flag evaluation lifecycle. These hooks can be used to perform side effects and mutate the context for purposes of the provider. Provider hooks are not configured or controlled by the `application author`. #### Requirement 2.3.1 > The provider interface **MUST** define a `provider hook` mechanism which can be optionally implemented in order to add `hook` instances to the evaluation life-cycle. ```java class MyProvider implements Provider { //... readonly hooks: Hook[] = [new MyProviderHook()]; // ..or alternatively.. getProviderHooks(): Hook[] { return [new MyProviderHook()]; } //... } ``` #### Requirement 2.3.2 > In cases of normal execution, the `provider` **MUST NOT** populate the `resolution details` structure's `error message` field, or otherwise must populate it with a null or falsy value. #### Requirement 2.3.3 > In cases of abnormal execution, the `resolution details` structure's `error message` field **MAY** contain a string containing additional detail about the nature of the error. ### 2.4 Initialization [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) #### Requirement 2.4.1 > The `provider` **MAY** define an initialization function which accepts the global `evaluation context` and an optional bound `domain`, which performs initialization logic relevant to the provider. Many feature flag frameworks or SDKs require some initialization before they can be used. They might require the completion of an HTTP request, establishing persistent connections, or starting timers or worker threads. The initialization function is an ideal place for such logic. The `domain` the provider is registered under is also supplied, allowing the provider to scope domain-specific behavior, such as partitioning a persistent cache, so that multiple providers sharing the same storage do not collide. A `provider` instance is initialized only once, even when bound to multiple `domains`; in that case the `domain` supplied is the one under which it was first registered. A provider that maintains `domain`-specific state can instead declare itself `domain-scoped` (see [Requirement 2.4.3](#requirement-243)), in which case it is restricted to a single `domain` and this ambiguity does not arise. The default provider, which is not bound to a domain, is initialized without one. ```java // MyProvider implementation of the initialize function defined in Provider class MyProvider implements Provider { //... // the global context and the bound domain are passed to the initialization function void initialize(EvaluationContext initialContext, @Nullable String domain) { this.domain = domain; /* A hypothetical initialization function: make an initial call doing some bulk initial evaluation, start a worker to do periodic updates */ this.flagCache = this.restClient.bulkEvaluate(initialContext); this.startPolling(); } //... } ``` #### Condition 2.4.2 > The provider defines an `initialize` function. ##### Conditional Requirement 2.4.2.1 > If the provider's `initialize` function fails to render the provider ready to evaluate flags, it **SHOULD** abnormally terminate. If a provider is unable to start up correctly, it should indicate abnormal execution by throwing an exception, returning an error, or otherwise indicating so by means idiomatic to the implementation language. If the error is irrecoverable (perhaps due to bad credentials or invalid configuration) the `PROVIDER_FATAL` error code should be used. see: [error codes](https://openfeature.dev/specification/types.md#error-code), [provider status](#28-provider-status) #### Requirement 2.4.3 > The `provider` **MAY** declare that it is `domain-scoped`, indicating that it maintains state specific to a single `domain`, such as a persistent cache, that cannot be shared across `domains`. Most providers are stateless with respect to their `domain` and can safely back multiple `domains` from a single instance. Providers that persist or cache `domain`-specific data need a stable, unambiguous `domain` to key that state on. By declaring itself `domain-scoped`, such a provider signals that the `API` must bind it to at most one `domain` (see [Requirement 1.1.8](https://openfeature.dev/specification/sections/flag-evaluation.md#condition-118)), guaranteeing the `domain` supplied to `initialize` is the only one the instance will ever serve. #### Requirement 2.4.4 > A `provider` that declares itself `domain-scoped` **MUST** accept the bound `domain` during initialization. A `domain-scoped` declaration is only meaningful if the provider consumes the `domain` it is given to scope its state. This is a contract on the provider; implementations may not be able to detect or reject a violation automatically, so it is not guaranteed to surface as a runtime error. ### 2.5. Shutdown [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) #### Requirement 2.5.1 > The provider **MAY** define a mechanism to gracefully shutdown and dispose of resources. ```java // MyProvider implementation of the dispose function defined in Provider class MyProvider implements Provider, AutoDisposable { //... void dispose() { // close connections, terminate threads or timers, etc... } ``` #### Requirement 2.5.2 > After a provider's `shutdown` function has terminated, the provider **SHOULD** revert to its uninitialized state. If a provider requires initialization, once it's shut down, it must transition to its uninitialized state. Some providers may allow reinitialization from this state. Providers not requiring initialization are assumed to be ready at all times. Providers in the process of initializing abort initialization if shutdown is called while they are still starting up. see: [initialization](#24-initialization) #### Requirement 2.5.3 > A Provider's `shutdown` function **SHOULD** be idempotent. If a provider's `shutdown` function has been called, subsequent calls (without an intervening call to `initialize`) should have no effect. see: [initialization](#24-initialization) ### 2.6. Provider context reconciliation [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) Static-context focused providers may need a mechanism to understand when their cache of evaluated flags must be invalidated or updated. An `on context changed` function can be defined which performs whatever operations are needed to reconcile the evaluated flags with the new context. #### Requirement 2.6.1 > The provider **MAY** define an `on context changed` function, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change. Especially in static-context implementations, providers and underlying SDKs may maintain state for a particular context. The `on context changed` function provides a mechanism to update this state, often by re-evaluating flags in bulk with respect to the new context. ```java // MyProvider implementation of the onContextChanged function defined in Provider class MyProvider implements Provider { //... onContextChanged(EvaluationContext oldContext, EvaluationContext newContext): void { // update context-sensitive cached flags, or otherwise react to the change in the global context } //... } ``` see: [provider status](#28-provider-status) Providers may maintain remote connections, timers, threads or other constructs that need to be appropriately disposed of. Provider authors may implement a `shutdown` function to perform relevant clean-up actions. Alternatively, implementations might leverage language idioms such as auto-disposable interfaces or some means of cancellation signal propagation to allow for graceful shutdown. ### 2.7. Tracking Support [![experimental](https://img.shields.io/static/v1?label=Status\&message=experimental\&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) Some flag management systems support tracking functionality, which can be used to associate feature flag evaluations with subsequent user actions or application state. See [tracking](https://openfeature.dev/specification/sections/tracking.md). #### Condition 2.7.1 > The `provider` **MAY** define a function for tracking the occurrence of a particular user action or application state, with parameters `tracking event name` (string, required), `evaluation context` (optional) and `tracking event details` (optional) which returns nothing. ```java class MyProvider implements Tracking { //... /** * Record a tracking event. */ public void track(String trackingEventName, EvaluationContext context, TrackingEventDetails details) { // perform side effects to record the event } //... } ``` The track function is a void function (function returning nothing). The track function performs side effects required to record the `tracking event` in question, which may include network activity or other I/O; this I/O should not block the function call. Providers should be careful to complete any communication or flush any relevant uncommitted tracking data before they shut down. See [shutdown](#25-shutdown). ### 2.8. Provider status [![hardening](https://img.shields.io/static/v1?label=Status\&message=hardening\&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) The SDK derives provider status from events emitted by the provider. Providers signal all state transitions by emitting the appropriate event; the SDK updates its internal status accordingly and runs associated handlers. Shutdown is the exception: the SDK initiates the `shutdown` call and infers the `NOT_READY` transition itself, so no event from the provider is required (see [Requirement 1.7.6](https://openfeature.dev/specification/sections/flag-evaluation.md#requirement-176)). Providers that do not define an `initialize` function are not required to emit events for initialization; see Condition 2.8.5. Requirement 2.8.1 applies to all provider status transitions; Requirements 2.8.2-2.8.4 apply only when the provider defines the corresponding lifecycle method. Where practical, SDKs should couple lifecycle methods with event support so providers defining lifecycle methods can emit the required events. see: [provider lifecycle management](https://openfeature.dev/specification/sections/flag-evaluation.md#17-provider-lifecycle-management), [provider events](https://openfeature.dev/specification/sections/events.md#51-provider-events) #### Requirement 2.8.1 > The provider **MUST** emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `on context changed`) and spontaneous transitions. Providers must not rely on the SDK to infer status from lifecycle method return values. Instead, the provider emits the appropriate event (e.g. `PROVIDER_READY` after successful initialization) to signal each transition. see: [provider events](https://openfeature.dev/specification/sections/events.md#51-provider-events), [provider event types](https://openfeature.dev/specification/types.md#provider-events) #### Requirement 2.8.2 > The provider **MUST** emit `PROVIDER_READY` before its `initialize` function terminates normally. The provider is the sole source of this event; the SDK does not synthesize it based on the return of `initialize`. see: [Requirement 1.1.2.4](https://openfeature.dev/specification/sections/flag-evaluation.md#requirement-1124) #### Requirement 2.8.3 > The provider **MUST** emit `PROVIDER_ERROR` before its `initialize` function terminates abnormally. The provider is the sole source of this event; the SDK does not synthesize it based on the return of `initialize`. If the error is irrecoverable, the error code must indicate `PROVIDER_FATAL`. see: [error codes](https://openfeature.dev/specification/types.md#error-code), [Requirement 1.1.2.4](https://openfeature.dev/specification/sections/flag-evaluation.md#requirement-1124) #### Requirement 2.8.4 > The provider **MUST** emit `PROVIDER_CONTEXT_CHANGED` if its `on context changed` function terminates normally, and `PROVIDER_ERROR` if it terminates abnormally. As with initialization, the provider is the sole source of these events; the SDK does not synthesize `PROVIDER_CONTEXT_CHANGED` or `PROVIDER_ERROR` based on the return of `on context changed`. The `on context changed` return (or thrown error) is treated by the SDK as a synchronization signal only; the status transition and handler invocation occur only when the SDK receives the provider-emitted event. see: [provider context reconciliation](#26-provider-context-reconciliation) #### Condition 2.8.5 > The provider does not define an `initialize` function. ##### Conditional Requirement 2.8.5.1 > The SDK **MUST** treat such providers as `READY` from registration and **MUST** run `PROVIDER_READY` handlers on their behalf. Such providers have no initialization to wait for and no associated state transition to signal. Nothing in this specification prevents such a provider from emitting `PROVIDER_ERROR` (or other events) spontaneously to signal a problem encountered outside of initialization; SDKs handle such events as outlined elsewhere. --- # 6. Tracking [![experimental](https://img.shields.io/static/v1?label=Status\&message=experimental\&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) ## Overview The `tracking API` enables the association of feature flag evaluations with subsequent actions or application states, in order to facilitate experimentation and analysis of the impact of feature flags on business objectives. Combined with hooks which report feature flag evaluations to the analytics platform in question, tracking can allow for robust experimentation even for flag management systems that don't support tracking directly. ```mermaid sequenceDiagram Evaluation API->>+Tracking Hook: evaluate Tracking Hook->>Analytics Platform: before Tracking Hook->>Analytics Platform: after Tracking Hook->>-Evaluation API: evaluate Evaluation API->>Analytics Platform: track ``` ### 6.1. Tracking API #### Condition 6.1.1 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](https://openfeature.dev/specification/glossary.md#dynamic-context-paradigm) ##### Conditional Requirement 6.1.1.1 > The `client` **MUST** define a function for tracking the occurrence of a particular action or application state, with parameters `tracking event name` (string, required), `evaluation context` (optional) and `tracking event details` (optional), which returns nothing. ```java // example tracking event recording that a subject reached a page associated with a business goal client.track("visited-promo-page", evaluationContext); // example tracking event recording that a subject performed an action associated with a business goal, with the tracking event details having a particular numeric value client.track("clicked-checkout", evaluationContext, new TrackingEventDetails(99.77)); // example tracking event recording that a subject performed an action associated with a business goal, with the tracking event details having a particular numeric value client.track("clicked-checkout", evaluationContext, new TrackingEventDetails(99.77).add("currencyCode", "USD")); ``` See [evaluation context](https://openfeature.dev/specification/types.md#evaluation-context), [tracking event details](#62-tracking-event-details). #### Condition 6.1.2 [![experimental](https://img.shields.io/static/v1?label=Status\&message=experimental\&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) > The implementation uses the static-context paradigm. see: [static-context paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) ##### Conditional Requirement 6.1.2.1 > The `client` **MUST** define a function for tracking the occurrence of a particular action or application state, with parameters `tracking event name` (string, required) and `tracking event details` (optional), which returns nothing. The track function is a void function (function returning nothing). Though it may be associated with network activity or other I/O, it need not be awaited by application authors. ```java // example tracking event recording that a subject reached a page associated with a business goal client.track("visited-promo-page"); // example tracking event recording that a subject performed an action associated with a business goal, with the tracking event details having a particular numeric value client.track("clicked-checkout", new TrackingEventDetails(99.77)); // example tracking event recording that a subject performed an action associated with a business goal, with the tracking event details having a particular numeric and some additional details client.track("clicked-checkout", new TrackingEventDetails(99.77).add("currencyCode", "USD")); ``` #### Requirement 6.1.3 > The evaluation context passed to the provider's track function **MUST** be merged in the order: API (global; lowest precedence) -> transaction -> client -> invocation (highest precedence), with duplicate values being overwritten. The SDK passes a merged evaluation context to the provider's track function similarly to the manner it does in resolvers. See: [context levels and merging](https://openfeature.dev/specification/sections/evaluation-context.md#32-context-levels-and-merging). #### Requirement 6.1.4 > If the client's `track` function is called and the associated provider does not implement tracking, the client's `track` function **MUST** no-op. ### 6.2. Tracking Event Details The `tracking event details` structure defines optional data pertinent to a particular `tracking event`. #### Requirement 6.2.1 > The `tracking event details` structure **MUST** define an optional numeric `value`, associating a scalar quality with an `tracking event`. `Value` is a well-defined field which some providers may map to equivalent numeric values in their API. See [provider tracking support](https://openfeature.dev/specification/sections/providers.md#27-tracking-support). #### Requirement 6.2.2 > The `tracking event details` **MUST** support the inclusion of custom fields, having keys of type `string`, and values of type `boolean | string | number | structure`. The `tracking event details` supports the addition of arbitrary fields, including nested objects, similar to the `evaluation context` and object-typed flag values. See [structure](https://openfeature.dev/specification/types.md#structure), [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md). --- # Types and Data Structures ## Overview This document outlines some of the common types and data structures defined by OpenFeature and referenced elsewhere in this specification. ### Boolean A logical true or false, as represented idiomatically in the implementation languages. ### String A UTF-8 encoded string. ### Number A numeric value of unspecified type or size. Implementation languages may further differentiate between integers, floating point numbers, and other specific numeric types and provide functionality as idioms dictate. ### Structure Structured data, presented however is idiomatic in the implementation language, such as JSON or YAML. ### Datetime A language primitive for representing a date and time, optionally including timezone information. If no timezone is specified, the date and time will be treated as UTC. ### Evaluation Details A structure representing the result of the [flag evaluation process](https://openfeature.dev/specification/glossary.md#evaluating-flag-values), and made available in the [detailed flag resolution functions](https://openfeature.dev/specification/sections/flag-evaluation.md#14-detailed-flag-evaluation), containing the following fields: * flag key (string, required) * value (boolean | string | number | structure, required) * error code ([error code](#error-code), optional) * error message (string, optional) * reason (string, optional) * variant (string, optional) * flag metadata ([flag metadata](#flag-metadata)) Note Some type systems feature useful constraints that can enhance the ergonomics of the `evaluation details` structure. For example, in the case of an unsuccessful evaluation, `error code`, `reason`, and `error message` will be set, and `variant` will not. If the type system of the implementation supports the expression of such constraints, consider defining them. ### Resolution Details A structure which contains a subset of the fields defined in the `evaluation details`, representing the result of the provider's [flag resolution process](https://openfeature.dev/specification/glossary.md#resolving-flag-values), including: * value (boolean | string | number | structure, required) * error code ([error code](#error-code), optional) * error message (string, optional) * reason (string, optional) * variant (string, optional) * flag metadata ([flag metadata](#flag-metadata), optional) #### Resolution Reason A set of pre-defined reasons is enumerated below: | Reason | Explanation | | --------------- | -------------------------------------------------------------------------------------------------------------------------------- | | STATIC | The resolved value is static (no dynamic evaluation). | | DEFAULT | The resolved value fell back to a pre-configured value (no dynamic evaluation occurred or dynamic evaluation yielded no result). | | TARGETING_MATCH | The resolved value was the result of a dynamic evaluation, such as a rule or specific user-targeting. | | SPLIT | The resolved value was the result of pseudorandom assignment. | | CACHED | The resolved value was retrieved from a cache. | | DISABLED | The resolved value was the result of the flag being disabled in the management system. | | UNKNOWN | The reason for the resolved value could not be determined. | | STALE | The resolved value is non-authoritative or possibly out of date | | ERROR | The resolved value was the result of an error. | Note The `reason` should not be limited to the reasons enumerated above. It can be any of the pre-defined reasons, or any string value. Some type systems have features which can increase the ergonomics of `reason`, for instance a union of pre-defined types with a string, or a rust-style enumeration which allows for enumerated values to have associated content. ```rust enum Reason { Static, Default, TargetingMatch, Split, Cached, Unknown, Stale, Error, Other(String) } let myReason = Reason::Other("my-reason".to_string()); ``` > \[!NOTE] The `resolution details` structure is not exposed to the Application Author. It defines the data which Provider Authors must return when resolving the value of flags. ### Error Code An enumerated error code represented idiomatically in the implementation language. | Error Code | Explanation | | --------------------- | ------------------------------------------------------------------------------------------- | | PROVIDER_NOT_READY | The value was resolved before the provider was initialized. | | FLAG_NOT_FOUND | The flag could not be found. | | PARSE_ERROR | An error was encountered parsing data, such as a flag configuration. | | TYPE_MISMATCH | The type of the flag value does not match the expected type. | | TARGETING_KEY_MISSING | The provider requires a targeting key and one was not provided in the `evaluation context`. | | INVALID_CONTEXT | The `evaluation context` does not meet provider requirements. | | PROVIDER_FATAL | The provider has entered an irrecoverable error state. | | GENERAL | The error was for a reason not enumerated above. | ### Evaluation Context See [evaluation context](https://openfeature.dev/specification/sections/evaluation-context.md). ### Evaluation Options A structure containing the following fields: * hooks (one or more [hooks](https://openfeature.dev/specification/sections/hooks.md), optional) * hook hints ([hook hints structure](https://openfeature.dev/specification/sections/hooks.md#42-hook-hints), optional) ### Flag Metadata A structure which supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean`, `string`, or `number`. This structure is populated by a provider for use by an [Application Author](https://openfeature.dev/specification/glossary.md#application-author) via the [Evaluation API](https://openfeature.dev/specification/glossary.md#evaluation-api) or an [Application Integrator](https://openfeature.dev/specification/glossary.md#application-integrator) via [hooks](https://openfeature.dev/specification/sections/hooks.md). ### Provider Status An enumeration of possible provider states. | Status | Explanation | | ------------- | -------------------------------------------------------------------------------------------------- | | NOT_READY | The provider has not been initialized. | | READY | The provider has been initialized, and is able to reliably resolve flag values. | | ERROR | The provider is initialized but is not able to reliably resolve flag values. | | STALE | The provider's cached state is no longer valid and may not be up-to-date with the source of truth. | | FATAL | The provider has entered an irrecoverable error state. | | RECONCILING\* | The provider is reconciling its state with a context change. | \* [static context (client-side) paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) only ### Provider Event Details A structure defining a provider event payload, including: * flags changed (string\[], optional) * message (string, optional) * error code ([error code](#error-code), optional) * event metadata ([event metadata](#event-metadata)) ### Event Details A structure defining an event payload, including: * provider name (string, required) * flags changed (string\[], optional) * message (string, optional) * error code ([error code](#error-code), optional) * event metadata ([event metadata](#event-metadata)) ### Event Metadata A structure supporting the addition of arbitrary event data. It supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean`, `string`, or `number`. ### Provider Events An enumeration of provider events. | Event | Explanation | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | PROVIDER_READY | The provider is ready to perform flag evaluations. | | PROVIDER_ERROR | The provider signaled an error. | | PROVIDER_CONFIGURATION_CHANGED | A change was made to the backend flag configuration. | | PROVIDER_STALE | The provider's cached state is no longer valid and may not be up-to-date with the source of truth. | | PROVIDER_RECONCILING\* | The context associated with the provider has changed, and the provider has not yet reconciled its associated state. | | PROVIDER_CONTEXT_CHANGED\* | The context associated with the provider has changed, and the provider has reconciled its associated state. | \* [static context (client-side) paradigm](https://openfeature.dev/specification/glossary.md#static-context-paradigm) only ### Handler Functions A function or method which can be associated with a `provider event`, and runs when that event occurs. It declares an `event details` parameter. ### Tracking Event Details A structure which supports definition of arbitrary properties, including nested objects, similar to the `evaluation context` and object-typed flag values. See [tracking event details](https://openfeature.dev/specification/sections/tracking.md#62-tracking-event-details), [evaluation context](#evaluation-context). ---