For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/feature_flags/server/go.md.
A documentation index is available at /llms.txt.
This product is not supported for your selected Datadog site. ().
Overview
This page describes how to instrument your Go application with the Datadog Feature Flags SDK. The Go SDK integrates with OpenFeature, an open standard for feature flag management, and uses the Datadog SDK’s Remote Configuration to receive flag updates in real time.
This guide explains how to install and enable the SDK, create an OpenFeature client, and evaluate feature flags in your application.
Prerequisites
Before setting up the Go Feature Flags SDK, ensure you have:
# Required: Enable the feature flags providerDD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true# Required: Service identificationDD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
Installation
Install the Datadog OpenFeature provider package:
go get github.com/DataDog/dd-trace-go/v2/openfeature
You also need the OpenFeature Go SDK:
go get github.com/open-feature/go-sdk/openfeature
Initialize the SDK
Start the Datadog SDK and register the Datadog OpenFeature provider. The SDK must be started first because it enables Remote Configuration, which delivers flag configurations to your application.
Blocking initialization
Use SetProviderAndWait to block evaluation until the initial flag configuration is received. This ensures flags are ready before your application starts handling requests.
packagemainimport("log""github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"ddopenfeature"github.com/DataDog/dd-trace-go/v2/openfeature""github.com/open-feature/go-sdk/openfeature")funcmain(){// Start the Datadog tracer (enables Remote Config)tracer.Start()defertracer.Stop()// Create the Datadog OpenFeature providerprovider,err:=ddopenfeature.NewDatadogProvider(ddopenfeature.ProviderConfig{})iferr!=nil{log.Fatalf("Failed to create provider: %v",err)}deferprovider.Shutdown()// Register the provider and wait for initialization (default 30s timeout)iferr:=openfeature.SetProviderAndWait(provider);err!=nil{log.Fatalf("Failed to set provider: %v",err)}// Create the OpenFeature clientclient:=openfeature.NewClient("my-service")// Your application code here}
To specify a custom timeout, use SetProviderAndWaitWithContext:
ctx,cancel:=context.WithTimeout(context.Background(),10*time.Second)defercancel()iferr:=openfeature.SetProviderAndWaitWithContext(ctx,provider);err!=nil{log.Fatalf("Failed to set provider: %v",err)}
Non-blocking initialization
Use SetProvider to register the provider without waiting. Flag evaluations return default values until the configuration is received.
packagemainimport("log""github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"ddopenfeature"github.com/DataDog/dd-trace-go/v2/openfeature""github.com/open-feature/go-sdk/openfeature")funcmain(){// Start the Datadog tracer (enables Remote Config)tracer.Start()defertracer.Stop()// Create the Datadog OpenFeature providerprovider,err:=ddopenfeature.NewDatadogProvider(ddopenfeature.ProviderConfig{})iferr!=nil{log.Fatalf("Failed to create provider: %v",err)}deferprovider.Shutdown()// Register the provider without waitingopenfeature.SetProvider(provider)// Create the OpenFeature clientclient:=openfeature.NewClient("my-service")// Your application code here// Flag evaluations return defaults until configuration is received}
Create a client
Create an OpenFeature client to evaluate flags. You can create multiple clients with different names for different parts of your application:
// Create a client for your applicationclient:=openfeature.NewClient("my-service")
Set the evaluation context
Define an evaluation context that identifies the user or entity for flag targeting. The evaluation context includes attributes used to determine which flag variations should be returned:
evalCtx:=openfeature.NewEvaluationContext("user-123",// Targeting key (typically user ID)map[string]interface{}{"email":"user@example.com","country":"US","tier":"premium","age":25,},)
The targeting key is used for consistent traffic distribution (percentage rollouts). Additional attributes enable targeting rules, such as “enable for users in the US” or “enable for premium tier users” in the example above.
Evaluate flags
After setting up the provider and creating a client, you can evaluate flags throughout your application. Flag evaluation is local and fast—the SDK uses locally cached configuration data, so no network requests occur during evaluation.
Each flag is identified by a key (a unique string) and can be evaluated with a typed method that returns a value of the expected type. If the flag doesn’t exist or cannot be evaluated, the SDK returns the provided default value.
Boolean flags
Use BooleanValue for flags that represent on/off or true/false conditions:
For numeric flags, use IntValue or FloatValue. These are appropriate when a feature depends on a numeric parameter such as a limit, percentage, or multiplier:
For structured data, use ObjectValue. This returns a value that can be type-asserted to maps or other complex types:
config,err:=client.ObjectValue(ctx,"feature-config",map[string]interface{}{"maxRetries":3,"timeout":30,},evalCtx)iferr!=nil{log.Printf("Error evaluating flag: %v",err)}// Type assert to access the configurationifconfigMap,ok:=config.(map[string]interface{});ok{maxRetries:=configMap["maxRetries"]timeout:=configMap["timeout"]// Use configuration values}
Flag evaluation details
When you need more than just the flag value, use the *ValueDetails methods. These return both the evaluated value and metadata explaining the evaluation:
Flag details help you debug evaluation behavior and understand why a user received a given value.
Testing
You can test against a dedicated Datadog test environment with the real DatadogProvider, or swap it for OpenFeature’s in-memory provider to control flag values directly in test code. This section shows the in-memory approach, which keeps tests hermetic and offline. The in-memory provider ships in the upstream go-sdk module under github.com/open-feature/go-sdk/openfeature/memprovider, so no additional dependency is required.
Register the in-memory provider under a named client rather than the default global provider. The default provider is process-global, which breaks t.Parallel() and leaks flag state between tests. A named client scopes the provider to each test.
packagecheckoutimport("context""testing""github.com/open-feature/go-sdk/openfeature""github.com/open-feature/go-sdk/openfeature/memprovider")funcTestNewCheckoutFlow(t*testing.T){cases:=[]struct{namestringtierstringwantbool}{{"premium user sees new flow","premium",true},{"free user sees legacy","free",false},}for_,tc:=rangecases{t.Run(tc.name,func(t*testing.T){evalByTier:=func(flagmemprovider.InMemoryFlag,flatCtxopenfeature.FlattenedContext)(any,openfeature.ProviderResolutionDetail){ifflatCtx["tier"]=="premium"{returnflag.Variants["on"],openfeature.ProviderResolutionDetail{Reason:openfeature.TargetingMatchReason,Variant:"on"}}returnflag.Variants[flag.DefaultVariant],openfeature.ProviderResolutionDetail{Reason:openfeature.DefaultReason,Variant:flag.DefaultVariant}}provider:=memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{"new-checkout-flow":{State:memprovider.Enabled,DefaultVariant:"off",Variants:map[string]any{"on":true,"off":false},ContextEvaluator:&evalByTier,},})name:="test-"+t.Name()iferr:=openfeature.SetNamedProviderAndWait(name,provider);err!=nil{t.Fatal(err)}t.Cleanup(func(){_=openfeature.SetNamedProviderAndWait(name,openfeature.NoopProvider{})})client:=openfeature.NewClient(name)got,err:=client.BooleanValue(context.Background(),"new-checkout-flow",false,openfeature.NewEvaluationContext("user-1",map[string]any{"tier":tc.tier}))iferr!=nil{t.Fatal(err)}ifgot!=tc.want{t.Errorf("got %v, want %v",got,tc.want)}})}}
ContextEvaluator is defined as *func(...) — a pointer to a function. Define the evaluator in a local variable and pass its address with &, as shown above. Omit ContextEvaluator entirely to always return DefaultVariant.
Further reading
Additional helpful documentation, links, and articles: