---
title: Feature Flags with OpenTelemetry
description: >-
  Add Datadog Feature Flags to an application that uses OpenTelemetry for
  tracing.
breadcrumbs: >-
  Docs > Feature Flags > Implementation Patterns > Feature Flags with
  OpenTelemetry
---

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

# Feature Flags with OpenTelemetry

{% 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 %}

You can add [Datadog Feature Flags](https://docs.datadoghq.com/feature_flags/server.md) to an application that uses [OpenTelemetry (OTel)](https://opentelemetry.io/) for tracing. Datadog supports two integration options. In both cases, feature flags evaluate correctly and your existing OpenTelemetry API instrumentation code remains the same.

Choose an integration option based on whether you can change your tracing provider initialization:

- **Option A:** Register the Datadog SDK as the TracerProvider
- **Option B:** Run the Datadog SDK alongside OpenTelemetry

If you can change your tracing provider initialization, Option A is recommended. If you cannot change your tracing provider, use Option B to adopt feature flags without changing your OTel trace pipeline.

## Prerequisites{% #prerequisites %}

Follow the Datadog [Server-Side Feature Flags](https://docs.datadoghq.com/feature_flags/server.md) installation guide for your language.

## Option A: Register the Datadog SDK as the TracerProvider{% #option-a-register-the-datadog-sdk-as-the-tracerprovider %}

In this option, the Datadog SDK replaces the OTel SDK as the `TracerProvider`. Your existing OpenTelemetry API instrumentation code — spans, attributes, events, and context propagation — stays the same.

{% alert level="warning" %}
Remove any existing OpenTelemetry TracerProvider and exporter initialization to avoid duplicate traces.
{% /alert %}

{% tab title=".NET" %}
In the `.env` file:

```bash
DD_TRACE_OTEL_ENABLED=true
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `Program.cs` file:

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

// Register the Datadog OpenFeature provider
var ffProvider = new DatadogProvider();
await Api.Instance.SetProviderAsync(ffProvider);

var client = Api.Instance.GetClient("my-service");

// Your existing OpenTelemetry API calls continue to work unchanged
```

For more detail, see [OpenTelemetry API Support for .NET](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=dot_net&platform=traces).
{% /tab %}

{% tab title="Go" %}
In the `.env` file:

```bash
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `main.go` file:

```go
import (
    "log"

    ddotel "github.com/DataDog/dd-trace-go/v2/ddtrace/opentelemetry"
    "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"
    "go.opentelemetry.io/otel"
)

func main() {
    tracer.Start()
    defer tracer.Stop()

    // Register the Datadog SDK as the OpenTelemetry TracerProvider
    otelProvider := ddotel.NewTracerProvider()
    defer otelProvider.Shutdown()
    otel.SetTracerProvider(otelProvider)

    // Register the Datadog OpenFeature provider
    flagProvider, err := ddopenfeature.NewDatadogProvider(ddopenfeature.ProviderConfig{})
    if err != nil {
        log.Fatalf("Failed to create provider: %v", err)
    }
    defer flagProvider.Shutdown()

    if err := openfeature.SetProviderAndWait(flagProvider); err != nil {
        log.Fatalf("Failed to set provider: %v", err)
    }

    client := openfeature.NewClient("my-service")

    // Your existing OpenTelemetry API calls continue to work unchanged
}
```

For more detail, see [OpenTelemetry API Support for Go](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=go&platform=traces).
{% /tab %}

{% tab title="Java" %}
In the `.env` file:

```bash
DD_TRACE_OTEL_ENABLED=true
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
DD_VERSION=<YOUR_APP_VERSION>
```

In the `Main.java` file:

```java
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.Client;
import datadog.trace.api.openfeature.Provider;

// Register the Datadog OpenFeature provider
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new Provider());
Client client = api.getClient("my-app");

/* Your existing OpenTelemetry API calls continue to work unchanged */
```

**Note**: Depend only on the OpenTelemetry API, not the OpenTelemetry SDK. For more detail, see [OpenTelemetry API Support for Java](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=java&platform=traces).
{% /tab %}

{% tab title="Node.js" %}
In the `.env` file:

```bash
DD_TRACE_OTEL_ENABLED=true
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.js` file:

```javascript
import tracer from 'dd-trace';
import { OpenFeature } from '@openfeature/server-sdk';
import * as otel from '@opentelemetry/api';

tracer.init({
  experimental: {
    flaggingProvider: {
      enabled: true,
    },
  },
});

// Register the Datadog SDK as the OpenTelemetry TracerProvider
const provider = new tracer.TracerProvider();
provider.register();

// Register the Datadog OpenFeature provider
OpenFeature.setProvider(tracer.openfeature);

// Your existing @opentelemetry/api calls continue to work unchanged:
const otelTracer = otel.trace.getTracer('my-service');
otelTracer.startActiveSpan('my-operation', (span) => { ... });
```

For more detail, see [OpenTelemetry API Support for Node.js](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=node_js&platform=traces).
{% /tab %}

{% tab title="Python" %}
In the `.env` file:

```bash
DD_TRACE_OTEL_ENABLED=true
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.py` file:

```python
from ddtrace import tracer
from openfeature import api
from ddtrace.openfeature import DataDogProvider

tracer.configure()

# Register the Datadog OpenFeature provider
provider = DataDogProvider()
api.set_provider(provider)
client = api.get_client()

# Your existing OpenTelemetry API calls continue to work unchanged
```

For more detail, see [OpenTelemetry API Support for Python](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=python&platform=traces).
{% /tab %}

{% tab title="Ruby" %}
In the `.env` file:

```bash
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.rb` file:

```ruby
require 'datadog'
require 'opentelemetry/sdk'
require 'datadog/opentelemetry'
require 'open_feature/sdk'
require 'datadog/open_feature/provider'

# Enable remote configuration and OpenFeature integration
Datadog.configure do |c|
  c.remote.enabled = true
  c.open_feature.enabled = true
end

# Register the Datadog OpenFeature provider
OpenFeature::SDK.configure do |c|
  c.set_provider_and_wait(Datadog::OpenFeature::Provider.new)
end

client = OpenFeature::SDK.build_client

# Your existing OpenTelemetry API calls continue to work unchanged
```

For more detail, see [OpenTelemetry API Support for Ruby](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=ruby&platform=traces).
{% /tab %}

{% tab title="PHP" %}
**Note**: The OpenFeature adapter requires PHP 8 or later.

In the `.env` file:

```bash
DD_TRACE_OTEL_ENABLED=true
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.php` file:

```php
<?php

require_once __DIR__ . '/vendor/autoload.php';

use DDTrace\OpenFeature\DataDogProvider;
use OpenFeature\OpenFeatureAPI;

// Register the Datadog OpenFeature provider
$api = OpenFeatureAPI::getInstance();
$api->setProvider(new DataDogProvider());

$client = $api->getClient('my-service');

// Your existing OpenTelemetry API calls continue to work unchanged
```

For more detail, see [OpenTelemetry API Support for PHP](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md?prog_lang=php&platform=traces).
{% /tab %}

## Option B: Run the Datadog SDK alongside OpenTelemetry{% #option-b-run-the-datadog-sdk-alongside-opentelemetry %}

In this option, your existing OTel SDK remains the `TracerProvider` for tracing. Add the Datadog SDK only for feature flag delivery through Remote Configuration. Your OTel SDK initialization, exporters, and instrumentation stay unchanged.

{% tab title=".NET" %}
In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `Program.cs` file:

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

// Register the Datadog OpenFeature provider
var provider = new DatadogProvider();
await Api.Instance.SetProviderAsync(provider);

var client = Api.Instance.GetClient("my-service");

// Your existing OpenTelemetry SDK initialization stays unchanged
```

{% /tab %}

{% tab title="Go" %}
In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `main.go` file:

```go
import (
    "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"
)

func main() {
    tracer.Start()
    defer tracer.Stop()

    // Register the Datadog OpenFeature provider
    flagProvider, err := ddopenfeature.NewDatadogProvider(ddopenfeature.ProviderConfig{})
    if err != nil {
        log.Fatalf("Failed to create provider: %v", err)
    }
    defer flagProvider.Shutdown()

    if err := openfeature.SetProviderAndWait(flagProvider); err != nil {
        log.Fatalf("Failed to set provider: %v", err)
    }

    client := openfeature.NewClient("my-service")

    // Your existing OpenTelemetry SDK initialization stays unchanged
}
```

{% /tab %}

{% tab title="Java" %}
In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
DD_VERSION=<YOUR_APP_VERSION>
```

In the `Main.java` file:

```java
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.Client;
import datadog.trace.api.openfeature.Provider;

// Register the Datadog OpenFeature provider
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new Provider());
Client client = api.getClient("my-app");

/* Your existing OpenTelemetry SDK initialization stays unchanged */
```

{% /tab %}

{% tab title="Node.js" %}
In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.js` file:

```javascript
import tracer from 'dd-trace';
import { OpenFeature } from '@openfeature/server-sdk';

tracer.init({
  plugins: false,
  experimental: {
    flaggingProvider: {
      enabled: true,
    },
  },
});

// Register the Datadog OpenFeature provider
OpenFeature.setProvider(tracer.openfeature);

// Your existing OpenTelemetry SDK initialization stays unchanged
```

{% /tab %}

{% tab title="Python" %}
In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.py` file:

```python
from ddtrace import tracer
from openfeature import api
from ddtrace.openfeature import DataDogProvider

tracer.configure()

# Register the Datadog OpenFeature provider
provider = DataDogProvider()
api.set_provider(provider)
client = api.get_client()

# Your existing OpenTelemetry SDK initialization stays unchanged
```

{% /tab %}

{% tab title="Ruby" %}
In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.rb` file:

```ruby
require 'datadog'
require 'open_feature/sdk'
require 'datadog/open_feature/provider'

Datadog.configure do |c|
  c.remote.enabled = true
  c.open_feature.enabled = true
end

# Register the Datadog OpenFeature provider
OpenFeature::SDK.configure do |c|
  c.set_provider_and_wait(Datadog::OpenFeature::Provider.new)
end

client = OpenFeature::SDK.build_client

# Your existing OpenTelemetry SDK initialization stays unchanged
```

{% /tab %}

{% tab title="PHP" %}
**Note**: The OpenFeature adapter requires PHP 8 or later.

In the `.env` file:

```bash
DD_APM_TRACING_ENABLED=false
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
DD_METRICS_OTEL_ENABLED=true
DD_SERVICE=<YOUR_SERVICE_NAME>
DD_ENV=<YOUR_ENVIRONMENT>
```

In the `app.php` file:

```php
<?php

require_once __DIR__ . '/vendor/autoload.php';

use DDTrace\OpenFeature\DataDogProvider;
use OpenFeature\OpenFeatureAPI;

// Register the Datadog OpenFeature provider
$api = OpenFeatureAPI::getInstance();
$api->setProvider(new DataDogProvider());

$client = $api->getClient('my-service');

// Your existing OpenTelemetry SDK initialization stays unchanged
```

{% /tab %}

## Evaluate feature flags{% #evaluate-feature-flags %}

After you initialize the provider, evaluate flags with the OpenFeature client. Flag evaluation uses locally cached configuration, so evaluations do not make network requests.

{% tab title=".NET" %}

```csharp
using OpenFeature.Model;

var evalCtx = EvaluationContext.Builder()
    .SetTargetingKey("user-123")
    .Set("companyID", "acme-corp")
    .Build();

var isEnabled = await client.GetBooleanValueAsync("my-flag", false, evalCtx);
```

{% /tab %}

{% tab title="Go" %}

```go
evalCtx := openfeature.NewEvaluationContext(
    "user-123",
    map[string]interface{}{
        "companyID": "acme-corp",
    },
)

enabled, err := client.BooleanValue(context.Background(), "my-flag", false, evalCtx)
```

{% /tab %}

{% tab title="Java" %}

```java
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.MutableContext;

EvaluationContext context = new MutableContext("user-123")
    .add("companyID", "acme-corp");

boolean isEnabled = client.getBooleanValue("my-flag", false, context);
```

{% /tab %}

{% tab title="Node.js" %}

```javascript
import { OpenFeature } from '@openfeature/server-sdk';

const client = OpenFeature.getClient();

const evaluationContext = {
  targetingKey: req.session?.userID ?? 'unknown',
  companyID: req.session?.companyID,
};

const isEnabled = await client.getBooleanValue(
  'my-flag',
  false,
  evaluationContext,
);
```

{% /tab %}

{% tab title="Python" %}

```python
from openfeature.evaluation_context import EvaluationContext

eval_ctx = EvaluationContext(
    targeting_key="user-123",
    attributes={"companyID": "acme-corp"},
)

is_enabled = client.get_boolean_value("my-flag", False, eval_ctx)
```

{% /tab %}

{% tab title="Ruby" %}

```ruby
context = OpenFeature::SDK::EvaluationContext.new(
  targeting_key: 'user-123',
  companyID: 'acme-corp',
)

is_enabled = client.fetch_boolean_value(
  flag_key: 'my-flag',
  default_value: false,
  evaluation_context: context,
)
```

{% /tab %}

{% tab title="PHP" %}

```php
use OpenFeature\implementation\flags\Attributes;
use OpenFeature\implementation\flags\EvaluationContext;

$context = new EvaluationContext(
    'user-123',
    new Attributes(['companyID' => 'acme-corp'])
);

$isEnabled = $client->getBooleanValue('my-flag', false, $context);
```

{% /tab %}

In both integration options, feature flags evaluate the same way. Flag configurations are delivered through the same Remote Configuration channel.

For complete setup instructions, typed getters, evaluation context requirements, and testing patterns, see your language's [server-side SDK documentation](https://docs.datadoghq.com/feature_flags/server.md).

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

- [Server-Side Feature Flags](https://docs.datadoghq.com/feature_flags/server.md)
- [OpenTelemetry API Support](https://docs.datadoghq.com/opentelemetry/instrument/dd_sdks/api_support.md)
- [Remote Configuration](https://docs.datadoghq.com/remote_configuration.md)
