---
title: .NET Feature Flags
description: Set up Datadog Feature Flags for .NET applications.
breadcrumbs: Docs > Feature Flags > Server-Side Feature Flags > .NET Feature Flags
---

> For the complete documentation index, see [llms.txt](https://docs.datadoghq.com/llms.txt).

# .NET Feature Flags

{% callout %}
# Important note for users on the following Datadog sites: app.ddog-gov.com, us2.ddog-gov.com

{% alert level="danger" %}
This product is not supported for your selected [Datadog site](https://docs.datadoghq.com/getting_started/site.md). ({% placeholder "user-datadog-site-name" /%}).
{% /alert %}

{% /callout %}

## Overview{% #overview %}

This page describes how to instrument your .NET application with the Datadog Feature Flags SDK. The .NET SDK integrates with [OpenFeature](https://openfeature.dev/), an open standard for feature flag management, and uses the Datadog .NET tracer (`dd-trace-dotnet`) to receive flag updates from the managed CDN or Agent Remote Configuration.

Starting in tracer version 3.54.0, new setups load flag configuration from the Datadog-managed CDN by default. This guide explains how to install the SDK, create an OpenFeature client, and evaluate feature flags in your application.

{% alert level="warning" %}
In version 3.54.0, agentless mode changes only flag configuration. Experiment exposure events still require a compatible local Agent or telemetry relay; direct Event Platform Proxy (EVP) fallback is not supported. Evaluation metrics require a separately configured OpenTelemetry export path. Without a telemetry path, only configuration delivery and local flag evaluation work.
{% /alert %}

## Prerequisites{% #prerequisites %}

For agentless configuration delivery, install the Datadog .NET tracer version **3.54.0 or later** and `Datadog.FeatureFlags.OpenFeature` version **2.3.1 or later**. The tracer must be loaded with [automatic instrumentation](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/dotnet-core.md); installing the OpenFeature provider alone is not sufficient. A separate Datadog Agent is not required to fetch flag configuration.

Set these environment variables in the application process before startup:

```bash
DD_API_KEY=<YOUR_API_KEY>
DD_SITE=<code class="js-region-param region-param" data-region-param="dd_site"></code>
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

Use a Datadog [API key](https://docs.datadoghq.com/account_management/api-app-keys.md#api-keys) and the site that hosts your organization, such as `datadoghq.com`. No Feature Flags enablement or source setting is required for a new setup. Initialize the Datadog OpenFeature provider in your application to start polling; installing or initializing the tracer alone does not start CDN polling. Evaluations use locally cached configuration and do not make network requests.

Flag evaluation metrics use a separately configured OpenTelemetry pipeline; enabling CDN delivery does not configure metrics export. See [Set Up Server-Side Flag Evaluation Metrics](https://docs.datadoghq.com/feature_flags/guide/server_flag_evaluation_metrics.md) and [Feature Flag Graphs](https://docs.datadoghq.com/feature_flags/concepts/flag_graphs.md).

### Use Agent Remote Configuration{% #use-agent-remote-configuration %}

For Agent-based delivery, use Datadog Agent 7.55 or later with [Remote Configuration](https://docs.datadoghq.com/agent/remote_config.md) enabled and an API key configured on the Agent. The minimum tracer versions are 3.36.0 for .NET 6+ and 3.38.0 for .NET Framework 4.6.2+.

With tracer 3.54.0 or later, select the source explicitly:

```bash
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE=remote_config
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

Earlier tracer versions use `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true`. In 3.54.0, this deprecated setting preserves Remote Configuration when neither the new enablement setting nor an explicit source is supplied. To migrate, remove the legacy setting, configure the application credentials above, and set `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE=agentless` if you already select a source explicitly. `DD_FEATURE_FLAGS_ENABLED=false` disables Feature Flags regardless of the selected source.

See [Configuration Sources](https://docs.datadoghq.com/feature_flags/concepts/configuration_sources.md) for polling, request timeout, custom endpoint, and migration settings. The default agentless polling interval is 30 seconds, the request timeout is 5 seconds, and provider initialization waits up to 30 seconds for the first configuration.

## Installation{% #installation %}

Install the Datadog [.NET SDK](https://www.nuget.org/packages/Datadog.Trace) and [OpenFeature SDK](https://www.nuget.org/packages/Datadog.FeatureFlags.OpenFeature) using NuGet:

```bash
dotnet add package Datadog.FeatureFlags.OpenFeature
dotnet add package OpenFeature
```

Or add them to your `.csproj` file:

In the `MyProject.csproj` file:

```xml
<ItemGroup>
  <PackageReference Include="Datadog.FeatureFlags.OpenFeature" />
  <PackageReference Include="OpenFeature" />
</ItemGroup>
```

If you enable flag evaluation metrics, you must also install the OpenTelemetry SDK and OTLP exporter:

```bash
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
```

Or add them to your `.csproj` file:

In the `MyProject.csproj` file:

```xml
<ItemGroup>
  <PackageReference Include="OpenTelemetry" />
  <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
</ItemGroup>
```

## Initialize the SDK{% #initialize-the-sdk %}

Register the Datadog OpenFeature provider with the OpenFeature API. The provider activates the selected configuration source in the Datadog .NET tracer.

### Blocking initialization{% #blocking-initialization %}

Use `SetProviderAsync` with `await` to block evaluation until the initial flag configuration is received. This ensures flags are ready before your application starts handling requests.

```csharp
using OpenFeature;
using Datadog.FeatureFlags.OpenFeature;

// Create and register the Datadog provider
var provider = new DatadogProvider();
await Api.Instance.SetProviderAsync(provider);

// Create an OpenFeature client
var client = Api.Instance.GetClient("my-service");

// Your application code here
```

### Non-blocking initialization{% #non-blocking-initialization %}

Use `SetProvider` to register the provider without waiting. Flag evaluations return default values until the configuration is received.

```csharp
using OpenFeature;
using Datadog.FeatureFlags.OpenFeature;

// Create and register the Datadog provider
var provider = new DatadogProvider();
Api.Instance.SetProvider(provider);

// Create an OpenFeature client
var client = Api.Instance.GetClient("my-service");

// Your application code here
// Flag evaluations return defaults until configuration is received
```

## Create a client{% #create-a-client %}

Create an OpenFeature client to evaluate flags. You can create multiple clients with different names for different parts of your application:

```csharp
// Create a client for your application
var client = Api.Instance.GetClient("my-service");
```

## Set the evaluation context{% #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:

{% alert level="warning" %}
Datadog Feature Flags requires evaluation context attributes to be flat primitive values: strings, numbers, and Booleans. Do not pass nested objects or arrays; they are not supported and can cause exposure data to be dropped.
{% /alert %}

```csharp
using OpenFeature.Model;

var evalCtx = EvaluationContext.Builder()
    .SetTargetingKey("user-123")  // Targeting key (typically user ID)
    .Set("email", "user@example.com")
    .Set("country", "US")
    .Set("tier", "premium")
    .Set("age", 25)
    .Build();
```

**Note:** In server-side applications, build the evaluation context once per request based on the current user, then pass the same context to all flag evaluations within that request. Only rebuild the context if user attributes change.

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{% #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{% #boolean-flags %}

Use `GetBooleanValueAsync` for flags that represent on/off or true/false conditions:

```csharp
var enabled = await client.GetBooleanValueAsync("new-checkout-flow", false, evalCtx);

if (enabled)
{
    ShowNewCheckout();
}
else
{
    ShowLegacyCheckout();
}
```

### String flags{% #string-flags %}

Use `GetStringValueAsync` for flags that select between multiple variants or configuration strings:

```csharp
var theme = await client.GetStringValueAsync("ui-theme", "light", evalCtx);

switch (theme)
{
    case "dark":
        SetDarkTheme();
        break;
    case "light":
        SetLightTheme();
        break;
    default:
        SetLightTheme();
        break;
}
```

### Numeric flags{% #numeric-flags %}

For numeric flags, use `GetIntegerValueAsync` or `GetDoubleValueAsync`. These are appropriate when a feature depends on a numeric parameter such as a limit, percentage, or multiplier:

```csharp
var maxItems = await client.GetIntegerValueAsync("cart-max-items", 20, evalCtx);

var discountRate = await client.GetDoubleValueAsync("discount-rate", 0.0, evalCtx);
```

### Object flags{% #object-flags %}

For structured data, use `GetObjectValueAsync`. This returns a value that can be used to access complex configuration:

```csharp
using OpenFeature.Model;

var defaultConfig = new Value(new Structure(new Dictionary<string, Value>
{
    ["maxRetries"] = new Value(3),
    ["timeout"] = new Value(30)
}));

var config = await client.GetObjectValueAsync("feature-config", defaultConfig, evalCtx);

// Access configuration values
var maxRetries = config.AsStructure?["maxRetries"].AsInteger ?? 3;
var timeout = config.AsStructure?["timeout"].AsInteger ?? 30;
```

### Flag evaluation details{% #flag-evaluation-details %}

When you need more than just the flag value, use the `*DetailsAsync` methods. These return both the evaluated value and metadata explaining the evaluation:

```csharp
var details = await client.GetBooleanDetailsAsync("new-feature", false, evalCtx);

Console.WriteLine($"Value: {details.Value}");
Console.WriteLine($"Variant: {details.Variant}");
Console.WriteLine($"Reason: {details.Reason}");
Console.WriteLine($"Error Type: {details.ErrorType}");
Console.WriteLine($"Error Message: {details.ErrorMessage}");
```

Flag details help you debug evaluation behavior and understand why a user received a given value.

## Waiting for provider initialization{% #waiting-for-provider-initialization %}

By default, the provider initializes asynchronously and flag evaluations return default values until the first flag configuration is received. If your application requires flags to be ready before handling requests, you can wait for the provider to initialize using event handlers:

```csharp
using OpenFeature;
using OpenFeature.Constant;

var taskCompletionSource = new TaskCompletionSource<bool>();

// Register event handler
Api.Instance.AddHandler(ProviderEventTypes.ProviderReady, (eventDetails) =>
{
    Console.WriteLine("Provider is ready");
    taskCompletionSource.SetResult(true);
});

Api.Instance.AddHandler(ProviderEventTypes.ProviderError, (eventDetails) =>
{
    Console.WriteLine($"Provider error: {eventDetails.Message}");
    taskCompletionSource.SetResult(false);
});

// Set provider
var provider = new DatadogProvider();
Api.Instance.SetProvider(provider);

// Wait for provider to be ready (with timeout)
var timeout = Task.Delay(TimeSpan.FromSeconds(30));
var completedTask = await Task.WhenAny(taskCompletionSource.Task, timeout);

if (completedTask == timeout)
{
    Console.WriteLine("Provider initialization timed out");
}

// Create client and evaluate flags
var client = Api.Instance.GetClient();
```

## Cleanup{% #cleanup %}

When your application exits, shut down the OpenFeature API to clean up resources:

```csharp
await Api.Instance.ShutdownAsync();
```

## Testing{% #testing %}

You can test against a dedicated Datadog test environment with the real `DatadogProvider`, or swap it for OpenFeature's `InMemoryProvider` to control flag values directly in test code. This section shows the in-memory approach, which keeps tests hermetic and offline. `InMemoryProvider` ships in the `OpenFeature` NuGet package (namespace `OpenFeature.Providers.Memory`), so no additional dependency is required beyond what is already installed for production.

`Api.Instance` is a singleton. Use xUnit's `IAsyncLifetime` to set the provider per test and tear it down in `DisposeAsync`, which avoids ordering-dependent tests. For faster suites that share setup, use `InMemoryProvider.UpdateFlagsAsync(...)` to mutate flag state between tests without re-registering the provider.

```csharp
using OpenFeature;
using OpenFeature.Model;
using OpenFeature.Providers.Memory;
using Xunit;

public class CheckoutFlagTests : IAsyncLifetime
{
    private FeatureClient _client = null!;

    public async Task InitializeAsync()
    {
        var flags = new Dictionary<string, Flag>
        {
            ["new-checkout-flow"] = new Flag<bool>(
                variants: new Dictionary<string, bool> { ["on"] = true, ["off"] = false },
                defaultVariant: "on"),
            ["ui-theme"] = new Flag<string>(
                variants: new Dictionary<string, string> { ["dark"] = "dark", ["light"] = "light" },
                defaultVariant: "light",
                contextEvaluator: ctx =>
                    ctx.GetValue("tier")?.AsString == "premium" ? "dark" : "light"),
        };

        await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));
        _client = Api.Instance.GetClient("test");
    }

    public Task DisposeAsync() => Api.Instance.ShutdownAsync();

    [Fact]
    public async Task NewCheckoutEnabledByDefault()
    {
        Assert.True(await _client.GetBooleanValueAsync("new-checkout-flow", false));
    }

    [Fact]
    public async Task PremiumUserGetsDarkTheme()
    {
        var ctx = EvaluationContext.Builder()
            .SetTargetingKey("u1")
            .Set("tier", "premium")
            .Build();
        Assert.Equal("dark", await _client.GetStringValueAsync("ui-theme", "light", ctx));
    }
}
```

The same pattern applies to NUnit (`[SetUp]`/`[TearDown]`) and MSTest (`[TestInitialize]`/`[TestCleanup]`). For ASP.NET Core integration tests, register the `InMemoryProvider` inside `WebApplicationFactory.ConfigureTestServices` before the application boots.

To avoid coupling tests to SDK internals, prefer swapping in `InMemoryProvider` over mocking the Datadog provider with Moq or similar libraries.

## Troubleshooting{% #troubleshooting %}

### Agentless configuration not working{% #agentless-configuration-not-working %}

- Verify that tracer 3.54.0 or later is loaded and the OpenFeature provider is initialized.
- Check `DD_API_KEY`, `DD_SITE`, and `DD_ENV` in the application process.
- Confirm `DD_FEATURE_FLAGS_ENABLED` is not `false`. Leave `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE` unset for a new setup, or explicitly set it to `agentless`. Remove the legacy `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED` setting when migrating.
- Allow outbound HTTPS to `ufc-server.ff-cdn.<DD_SITE>`.
- Enable `DD_TRACE_DEBUG=true` and check tracer logs for authentication, timeout, or malformed configuration errors.

Before the first valid configuration, evaluations return caller defaults. After successful initialization, transient delivery failures retain the last valid configuration.

### Remote Configuration not working{% #remote-configuration-not-working %}

Verify the following to ensure that Remote Configuration is working:

- Datadog Agent is the required version
- Remote Configuration is enabled on the Agent
- `DD_SERVICE` and `DD_ENV` environment variables are set
- The SDK can communicate with the Agent

### Async evaluation errors{% #async-evaluation-errors %}

The .NET OpenFeature SDK uses async methods for all flag evaluations. Ensure you're using `await` or properly handling the returned `Task`:

```csharp
// Correct: Using await
var enabled = await client.GetBooleanValueAsync("flag-key", false, context);

// Incorrect: Not awaiting (will not work as expected)
var enabled = client.GetBooleanValueAsync("flag-key", false, context);
```

## Further reading{% #further-reading %}

Additional helpful documentation, links, and articles:

- [Server-Side Feature Flags](https://docs.datadoghq.com/feature_flags/server.md)
- [.NET Tracing](https://docs.datadoghq.com/tracing/trace_collection/dd_libraries/dotnet-core.md)
- [Set Up Server-Side Flag Evaluation Metrics](https://docs.datadoghq.com/feature_flags/guide/server_flag_evaluation_metrics.md)
- [Set Up APM Trace Enrichment for Feature Flags](https://docs.datadoghq.com/feature_flags/guide/apm_trace_enrichment.md)
- [Feature Flag Graphs](https://docs.datadoghq.com/feature_flags/concepts/flag_graphs.md)
