Skip to content

DataDog/dd-flag-migration

Repository files navigation

Datadog Feature Flag Migration Tool

A CLI tool for migrating feature flags from your current provider into Datadog Feature Flags, with side-by-side evaluation to verify the migration before you switch over.

Supported providers: Eppo, LaunchDarkly


Prerequisites


Installation

Run without installing using npx:

# migrate flags
npx @datadog/dd-flag-migration migrate

# evaluate migrated flags
npx @datadog/dd-flag-migration evaluate

Contributing / running from source

See CONTRIBUTING.md.


Credentials you'll need

Credentials are read from environment variables. Set them in your shell (or .envrc, .env loader, secret manager, etc.) before running migrate or evaluate. If any required variable is missing, the tool prints a list of the missing names to stderr and exits with code 1.

Required for migrate

Variable Required when Where to find it
DD_API_KEY always Datadog → Organization Settings → API Keys
DD_APP_KEY always Datadog → Organization Settings → Application Keys
EPPO_API_KEY provider = Eppo Eppo → Configuration → API Keys
LAUNCHDARKLY_API_KEY provider = LaunchDarkly LaunchDarkly → Account settings → Authorization → Access tokens

Your LaunchDarkly access token needs Reader role permissions (or a custom role with viewProject access) to read projects, environments, and flag configurations.

EPPO_* variables are checked only when you select Eppo as the source provider. LAUNCHDARKLY_* variables are checked only when you select LaunchDarkly. You don't need to set both.

Required for evaluate

Variable Required when Where to find it
DD_CLIENT_TOKEN always Datadog → Organization Settings → Client Tokens
EPPO_SDK_KEY migration was from Eppo Eppo → SDK Keys (server SDK key, one per environment)
LAUNCHDARKLY_API_KEY migration was from LaunchDarkly (preferred) LaunchDarkly → Account settings → Authorization → Access tokens

Datadog Application Key permissions

Your Datadog Application Key must have the following scopes enabled:

Scope Description
feature_flag_approvals_override Ability to bypass Feature Flag approval requirements
feature_flag_config_read View Feature Flag Configurations
feature_flag_config_write Edit Feature Flag Configurations
feature_flag_environment_config_read Ability to view Feature Flag Environment settings
teams_read View Teams (required for team-based access controls)

To set these permissions, go to Organization Settings → Application Keys, select your key, and enable the scopes listed above. The feature flag scopes are under the Feature Flags section; teams_read is under Teams.

Examples

Migrate from Eppo

export DD_API_KEY=...
export DD_APP_KEY=...
export EPPO_API_KEY=...

npx @datadog/dd-flag-migration migrate

Evaluate an Eppo migration

export DD_API_KEY=...
export DD_APP_KEY=...
export DD_CLIENT_TOKEN=...
export EPPO_SDK_KEY=...

npx @datadog/dd-flag-migration evaluate

Migrate from LaunchDarkly

export DD_API_KEY=...
export DD_APP_KEY=...
export LAUNCHDARKLY_API_KEY=...

npx @datadog/dd-flag-migration migrate

Evaluate a LaunchDarkly migration

If LAUNCHDARKLY_API_KEY is already set (from running migrate), the SDK key is fetched automatically:

export DD_API_KEY=...
export DD_APP_KEY=...
export DD_CLIENT_TOKEN=...
export LAUNCHDARKLY_API_KEY=...   # SDK key fetched automatically

npx @datadog/dd-flag-migration evaluate

Or set the SDK key directly if you don't have the API key available:

export DD_API_KEY=...
export DD_APP_KEY=...
export DD_CLIENT_TOKEN=...
export LAUNCHDARKLY_SDK_KEY=...   # server-side key, scoped to one environment

npx @datadog/dd-flag-migration evaluate

Step 1 — Migrate flags

npx @datadog/dd-flag-migration migrate

The tool will walk you through:

  1. Select your provider — Eppo or LaunchDarkly
  2. Map environments — link each source environment (e.g. production) to the corresponding Datadog environment
  3. Select flags — choose which flags to migrate; flags already in Datadog are marked. Press Tab to open the advanced-filter screen and narrow the list by category (see Advanced filtering), then Ctrl+A to select all remaining flags
  4. Confirm and migrate — flags are created in Datadog and enabled in the mapped environments. A progress bar tracks migration status in real time

API keys are read from environment variables (see Credentials). Pass --datadog-site=<site> to set the Datadog site without a prompt. For fully scripted runs, see Non-interactive mode below.

When the migration completes, a record is saved to ~/.dd-flag-migration/migration-<timestamp>.json. In interactive mode you'll be prompted to export results to an .xlsx file; in non-interactive mode pass --export=true to generate one.

Large migrations

For large flag sets, the tool supports splitting work across multiple runs:

  • Progress bar — a sticky progress bar shows how many flags have been migrated so far, updating in real time
  • Tab to filter — during flag selection, press Tab to open the advanced-filter screen and narrow the list by category, such as not-yet-migrated. Combined with Ctrl+A, this makes it easy to select only the remaining flags for the next run. See Advanced filtering
  • Ctrl+C to save progress — pressing Ctrl+C during migration saves a partial migration file (~/.dd-flag-migration/migration-<timestamp>.json) with all flags that completed successfully before the interruption. You can resume later by filtering to not-yet-migrated with Tab

Advanced filtering

During flag selection, press Tab to open a multi-select filter screen. Categories start unchecked, which means no category filter is applied and all flags remain visible. Check one or more categories to narrow the flag list, then press Enter to apply the filter selection and return to flag selection, or Escape to cancel filter changes. Checking every category is equivalent to applying no category filter. Any selected flags that no longer match the applied filters are automatically unselected on return.

The available categories are:

  • new — any environment — LaunchDarkly reports new for at least one non-archived environment (LaunchDarkly only)
  • active — any environment — LaunchDarkly reports active for at least one non-archived environment (LaunchDarkly only)
  • inactive — all environments — LaunchDarkly reports inactive for every non-archived environment whose status could be loaded (LaunchDarkly only)
  • launched — any environment — LaunchDarkly reports launched for at least one non-archived environment (LaunchDarkly only)
  • previously-migrated — flag — a matching flag already exists in Datadog; its targeting rules may still differ (both providers)
  • not-yet-migrated — flag — no matching flag exists in Datadog yet (both providers)

The four lifecycle categories are derived from LaunchDarkly flag statuses, which are tracked per environment. Environment selection still controls what gets migrated; lifecycle filters look across all non-archived LaunchDarkly environments in the project so they can answer whether a flag appears to be used anywhere. If a status fetch fails, the tool does not treat that missing data as inactive. Eppo does not expose flag usage-recency data, so only the migration-state categories are available when migrating from Eppo.

LaunchDarkly-specific workflow

When migrating from LaunchDarkly, the tool adds these steps:

  1. Select a LaunchDarkly project — flags in LaunchDarkly are scoped to a project, so you pick one project at a time
  2. Select LaunchDarkly environments — choose which environments within that project to migrate
  3. Link environments — map each selected LaunchDarkly environment to a Datadog environment
  4. Select flags — flags already in Datadog are shown with a checkmark and will have their targeting synced for new environments rather than being re-created

The tool translates LaunchDarkly targeting rules, individual user targets, percentage rollouts, and fallthrough variations into equivalent Datadog targeting filters. Before migrating flags, the tool runs a segment migration phase that converts referenced LaunchDarkly segments into Datadog saved filters and substitutes them into targeting rules. Flags that use unsupported operators (before, after) are automatically skipped with an explanation. Flags with prerequisites are migrated with a warning, since Datadog does not enforce prerequisites.

Non-interactive mode

Pass --interactive=false to run the migration entirely from CLI arguments, with no prompts. This is useful for scripted or CI environments.

Non-interactive migrations write a JSON result document to stdout. Status messages, progress output, and export messages are written to stderr so stdout can be piped into tools such as jq.

Required flags

Flag Description
--provider <Eppo|LaunchDarkly> Source provider (case-insensitive)
--datadog-site <site> Datadog site (e.g. datadoghq.com)
--env-map <source,target> Map a source environment to a Datadog environment. Repeat for each environment
--feature-flag <key> Flag key to migrate. Repeat for each flag. For LaunchDarkly, use <source-key>,<datadog-key> to rename the Datadog flag
--project <key> LaunchDarkly project key (LaunchDarkly only)

Optional flags

Flag Description
--dry-run Preview changes without writing to Datadog
--export=<bool> Export results to an .xlsx file after migration (default: false)

Examples

Migrate two LaunchDarkly flags across two environments:

npx @datadog/dd-flag-migration migrate --interactive=false \
  --provider LaunchDarkly \
  --project my-ld-project \
  --datadog-site datadoghq.com \
  --env-map Production,Production \
  --env-map Staging,QA \
  --feature-flag flag-one \
  --feature-flag flag-two

Rename a LaunchDarkly flag while migrating it:

npx @datadog/dd-flag-migration migrate --interactive=false \
  --provider LaunchDarkly \
  --project my-ld-project \
  --datadog-site datadoghq.com \
  --env-map Production,Production \
  --feature-flag my-flag-1,my-renamed-flag-1

Migrate Eppo flags (no project key required):

npx @datadog/dd-flag-migration migrate --interactive=false \
  --provider Eppo \
  --datadog-site datadoghq.com \
  --env-map production,Production \
  --feature-flag my-flag

Dry run

To preview what would be created without making any changes:

npx @datadog/dd-flag-migration migrate --dry-run

This writes the full list of API requests that would be sent to a dry-run-<timestamp>.json file in the current directory.


Step 2 — Evaluate the migration

Once flags have been migrated, run the evaluation to compare how flags are evaluated in Eppo vs. Datadog for the same inputs:

npx @datadog/dd-flag-migration evaluate

The tool will:

  1. Select a migration file — pick from previous migrations (most recent first)
  2. Select a Datadog environment — choose which environment to evaluate against
  3. Enter a test subject ID — a user ID (or any string) to use for flag evaluation
  4. Run evaluations — the tool generates test cases from each flag's targeting rules and compares the provider and Datadog results side by side

Datadog and provider credentials are read from environment variables (see Credentials).

Results are displayed in a table showing the Eppo value, Datadog value, migration status, and whether the flag is enabled. Matching values are shown in green; differences in yellow.

You can optionally export the full results to an .xlsx file.

Flags

Flag Description
--use-latest-migration Skip the migration file selector and use the most recent
--test-subject-id=<id> Set the subject ID non-interactively
--flag-environment=<name> Set the Datadog environment name non-interactively
--datadog-site=<site> Set the Datadog site non-interactively

Example for scripted use:

npx @datadog/dd-flag-migration evaluate \
  --use-latest-migration \
  --test-subject-id=user-123 \
  --flag-environment=production \
  --datadog-site=datadoghq.com

Configuration

The only setting persisted to ~/.dd-flag-migration/config.json is your Datadog site (so you don't have to re-enter it on every run). Credentials are never read from or written to this file — set them as environment variables instead.

Non-US Datadog sites

If your Datadog organization is on a regional site (EU, US3, US5, etc.), pass the site for a single run:

npx @datadog/dd-flag-migration evaluate --datadog-site=datadoghq.eu

To save a default site for interactive runs, add the site to your config:

{
  "datadogSite": "datadoghq.eu"
}
Site datadogSite value
US1 (default) datadoghq.com
EU datadoghq.eu
US3 us3.datadoghq.com
US5 us5.datadoghq.com
AP1 ap1.datadoghq.com

Unsupported features

Some LaunchDarkly and Eppo features have no direct equivalent in Datadog. The tool handles each case automatically — either by skipping the affected flag entirely or by applying the closest supported equivalent and recording the adjustment in the migration results.

Flags that are skipped

Feature Provider Reason
Dependent flags (prerequisites) LaunchDarkly Datadog does not enforce flag prerequisites. Flags that depend on another flag being in a specific state are skipped.
Date targeting (before / after operators) LaunchDarkly Date-based targeting conditions have no equivalent in Datadog targeting filters. Flags that use these operators are skipped.
Archived flags LaunchDarkly Archived flags are excluded from the migration entirely.
BANDIT and LAYER flag types Eppo These flag types are not yet supported and are skipped.

Flags that are migrated with adjustments

Feature Provider How it's handled
SEMVER targeting on server-side flags LaunchDarkly SEMVER comparisons are a client-SDK feature in Datadog. Flags that use SEMVER targeting are automatically migrated with the CLIENT distribution channel, regardless of how they were originally configured. A warning is recorded in the migration results.
JSON variants that are top-level arrays LaunchDarkly, Eppo Datadog requires JSON variant values to be objects. Array-valued variants are automatically wrapped: [...] becomes { "value": [...] }. A warning is recorded in the migration results.

Features with limited evaluation support

Feature Provider Behavior
Mobile context kinds (ld_application, ld_device) LaunchDarkly These context kinds are auto-populated by LaunchDarkly's mobile client SDKs and cannot be evaluated by the server-side Node.js SDK used by this tool. Targeting rules that use them are shown as not evaluated during the evaluation step. The migration itself is correct — targeting rules are translated using the same prefixed attribute format (e.g. ld_application.versionName).

How it works

Migration

Eppo

For each selected flag, the tool:

  • Reads the flag's variations, targeting filters, and targeting rules from Eppo
  • Creates an equivalent flag in Datadog via the Feature Flags API
  • Enables the flag in the Datadog environments that correspond to active Eppo environments

Flags of type BANDIT or LAYER are skipped (not yet supported).

LaunchDarkly

For each selected flag, the tool:

Before processing individual flags, the tool runs a segment migration phase:

  • Scans all selected flags for segmentMatch clauses and collects the referenced segment keys per environment
  • Fetches those segments from the LaunchDarkly API
  • Checks existing Datadog saved filters for already-migrated segments (idempotency via migration_metadata)
  • Creates a Datadog saved filter for each segment — using LIST type for pure key-inclusion segments and RULES type for all others
  • Builds a negated variant for any segment referenced with negate: true, using De Morgan's law to derive the inverse rules
  • Handles cross-project name conflicts interactively (skip or add a prefix)
  • Returns a lookup map used by the flag targeting step to substitute saved filter references in place of segmentMatch clauses

For each selected flag, the tool:

  • Reads the flag's variations, targeting rules, individual targets, and rollout configuration from LaunchDarkly
  • Maps the flag type (boolean or multivariate) to the corresponding Datadog value type (BOOLEAN, STRING, NUMERIC, or JSON)
  • Converts individual user targets into targeting filters with ONE_OF conditions on the key attribute
  • Translates each targeting rule's clauses into Datadog targeting rule conditions, mapping operators like in, contains, startsWith, endsWith, matches, and semver comparisons to their Datadog equivalents; replaces segmentMatch clauses with the saved filter IDs created in the segment phase
  • Converts percentage rollouts from LaunchDarkly's 100,000-weight scale to Datadog's 0-100 scale
  • Creates a fallthrough (default) targeting filter for the environment
  • For flags that already exist in Datadog, syncs targeting for newly mapped environments instead of re-creating the flag
  • Enables the flag in Datadog environments where it was enabled (on: true) in LaunchDarkly

Archived flags and flags using unsupported operators (before, after) are skipped automatically. Individual segment rules that use unsupported features (multi-context membership, nested segmentMatch, or negation explosions) are skipped with a warning; the flags that reference them are still migrated with their other targeting rules intact.

Evaluation

The evaluation tool generates test cases automatically from each flag's targeting rules — producing inputs that should match each rule and inputs that should not. It then calls the source provider's SDK and the Datadog feature flag CDN with the same subject ID and attributes, and compares the results.

This lets you verify that flag targeting logic was translated correctly before you cut over your application.

LaunchDarkly — mobile context kinds

Flags that target ld_application or ld_device context kinds (auto-populated by LaunchDarkly's mobile client SDKs) cannot be evaluated via the Node.js server-side SDK used by this tool. Test cases for those rules are shown as not evaluated (dimmed) with an explanatory note. The migration itself is correct — the targeting rules are translated into Datadog using the same prefixed attribute format (e.g. ld_application.versionName).

About

Migrates your feature flags to Datadog

Resources

License

Contributing

Stars

6 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors