Importing from LaunchDarkly

Copy a LaunchDarkly project into featflags, keep every user in the variant they had, and check every evaluation against LaunchDarkly before you switch.

The CLI copies a LaunchDarkly project into a featflags project: environments, context kinds, flags with their targeting in each environment, segments and big segments. Flags keep LaunchDarkly’s way of placing users in rollouts, so nobody changes variant when you switch. Running it again copies only what changed, so you can keep both in step while you move your services over one by one.

1. Plan

You need a LaunchDarkly access token that can read the project, and a featflags API token that can create projects and flags (an admin; see the CLI). Start with a dry run, which writes nothing and lists what the import would create or change, per flag and environment, and anything it can’t import:

export LD_API_TOKEN=api-…                              # LaunchDarkly access token
export LD_BASE_URL=https://app.eu.launchdarkly.com     # only for LaunchDarkly's EU or federal instances
featflags import launchdarkly --ld-project acme --to-project acme --dry-run

--envs production,staging limits it to some environments, and --json prints the plan, how variations map to variants, and what’s unsupported as one JSON document.

2. Import and verify

featflags import launchdarkly --ld-project acme --to-project acme --verify contexts.jsonl

It creates the project and any missing environment, and writes every flag and segment as an ordinary change, commented Imported from LaunchDarkly acme. --strict stops before writing anything if something can’t be imported.

--verify takes a file of sample contexts, one JSON object per line, in LaunchDarkly’s shape:

{"kind":"user","key":"u-alice","email":"alice@acme.com","plan":"pro"}
{"kind":"multi","user":{"key":"u-bob"},"org":{"key":"o-acme","plan":"team"}}

For each environment and context it asks LaunchDarkly what every imported flag serves, evaluates the same flags in featflags, and reports every flag whose value or reason differs, with both sides. Any difference makes it exit with code 3, so you can run it in CI. Use contexts that look like your real traffic: one per plan, country, platform and segment you target on.

3. Switch your SDKs

Point your services at featflags one at a time, with the environment’s keys: client keys where you used LaunchDarkly’s client-side ID or mobile key, server keys where you used its SDK key. Contexts work the same way, so the attributes you pass don’t change. Many featflags SDKs also have an OpenFeature provider, if your code already uses OpenFeature.

Until every service has moved, run the import again after changes in LaunchDarkly: it writes only what changed on either side.

How things map

LaunchDarkly featflags
Flag Flag, with LaunchDarkly’s bucketing and salt, its description, client-side availability, and whether it’s temporary.
Archived flag Imported, then archived.
Variations Variants, in order, named after the variation (Blue Sky becomes blue-sky), or by position when a name is missing or shared.
On, off variation, fallthrough On or off, off variant, default.
Prerequisites Prerequisites.
Individual and context targets Individual targets, in the same order.
Rules and clauses Rules and conditions, with their ids, negation and context kinds.
Percentage rollouts Rollouts, with the same context kind, bucketing attribute and seed.
Segments Segments: rules, and included and excluded keys.
Big segments Segments with an uploaded list of their members, exported from LaunchDarkly for you.

Segments belong to an environment in LaunchDarkly and to a project in featflags. A segment that’s the same in every environment keeps its key; one that differs becomes <key>.<environment>, and each environment’s flags use their own.

Clause operators map one to one: in, startsWith, endsWith, contains, matches, the number comparisons, before and after, the semver comparisons, and segmentMatch.

What isn’t imported

These are reported with the flag or segment and environment they’re in, and keep that whole flag or segment out of the import, along with every flag that depends on it, so what’s imported always evaluates as it did:

  • Migration flags, experiment rollouts, and rules without a variation or rollout to serve.
  • Nested attribute references such as /address/city, and regular expressions outside featflags’ supported syntax.
  • Segment rules that include a percentage of contexts or reference other segments, big segments with rules that exclude contexts, and synced segments (Amplitude, Segment and others), which LaunchDarkly keeps in sync and an import would copy only once.

One difference stays, noted on every flag with a rollout: LaunchDarkly gives a context that lacks the rollout’s kind or attribute the first variation (or the first bucket), while featflags skips that rule, or serves the off variant from the default. Contexts that have the attribute land exactly where they did.

In an environment that requires approval in featflags, the import stops at the first change held for review. Import with an admin’s token in an environment where admins may bypass approvals, or turn approvals on after the import.