Skip to main content

Migrate from LaunchDarkly

This guide walks you through migrating feature flags, environments, and targeting rules from LaunchDarkly to FeatureSignals using the built-in migration API.

Prerequisites

Before starting, ensure you have:

  1. FeatureSignals running with a target project created (see Installation)
  2. LaunchDarkly Admin API token with *:admin scope and read access to your source project
  3. Your LaunchDarkly project key (found in Project Settings)
  4. Your FeatureSignals target project ID (found in Project Settings → General)
  5. A test environment to validate the migration before touching production

Obtaining a LaunchDarkly API Token

  1. Log in to your LaunchDarkly dashboard
  2. Navigate to Settings → Authorization
  3. Click Create Token
  4. Set the name to featuresignals-migration
  5. Select the Admin role (or *:admin for fine-grained scopes)
  6. Click Create Token
  7. Copy the token immediately — it will not be shown again

Step 1: Discover Available Providers

Verify that the LaunchDarkly importer is registered:

curl -X POST https://api.featuresignals.com/v1/migration/providers \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json"

Expected response includes launchdarkly in the providers list:

{
"providers": [
{
"name": "launchdarkly",
"display_name": "LaunchDarkly",
"capabilities": ["flags", "environments", "segments"]
}
]
}

Step 2: Validate Connection

Test your LaunchDarkly API credentials:

curl -X POST https://api.featuresignals.com/v1/migration/connect \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"provider": "launchdarkly",
"api_key": "api-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"base_url": "https://app.launchdarkly.com",
"project_key": "your-ld-project-key"
}'

A successful response indicates the API can reach LaunchDarkly and has the necessary permissions:

{
"status": "connected",
"provider": "launchdarkly",
"project_key": "your-ld-project-key"
}

Step 3: Analyze the Source

Before importing, run a dry-run analysis to see what will be migrated:

curl -X POST https://api.featuresignals.com/v1/migration/analyze \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"provider": "launchdarkly",
"api_key": "api-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"base_url": "https://app.launchdarkly.com",
"project_key": "your-ld-project-key",
"target_project_id": "proj_abc123"
}'

Response:

{
"status": "analyzed",
"summary": {
"total_flags": 47,
"total_environments": 4,
"total_segments": 12,
"source_system": "launchdarkly"
},
"details": {
"boolean_flags": 35,
"string_flags": 8,
"number_flags": 3,
"json_flags": 1,
"environments": ["development", "staging", "production", "canary"]
}
}

Step 4: Execute the Migration

Once you've reviewed the analysis, start the migration:

curl -X POST https://api.featuresignals.com/v1/migration/execute \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"provider": "launchdarkly",
"api_key": "api-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"base_url": "https://app.launchdarkly.com",
"project_key": "your-ld-project-key",
"target_project_id": "proj_abc123"
}'

Response:

{
"status": "started",
"migration_id": "mig_xxx123",
"total_flags": 47,
"total_environments": 4
}

Step 5: Monitor Progress

Check migration status:

curl -X GET https://api.featuresignals.com/v1/migration/status/mig_xxx123 \
-H "Authorization: Bearer YOUR_JWT"

Response during migration:

{
"status": "in_progress",
"migration_id": "mig_xxx123",
"flags_imported": 23,
"flags_total": 47,
"environments_imported": 4,
"segments_imported": 8,
"errors": []
}

Mapping Reference

Flag Types

LaunchDarkly types are mapped to FeatureSignals types as follows:

LaunchDarkly KindFeatureSignals FlagTypeNotes
booleanbooleanTwo variations: true, false
stringstringVariations preserved as values
numbernumberNumeric variations preserved
jsonjsonJSON variations preserved

Flag Categories

LaunchDarkly does not have a built-in category system, so all imported flags are assigned release as the default category. You can adjust this in the FeatureSignals dashboard after migration.

Operator Mapping

LaunchDarkly OperatorFeatureSignals OperatorNegation
ininnotIn when negated
startsWithstartsWithDirect mapping
endsWithendsWithDirect mapping
containscontainsDirect mapping
greaterThangtDirect mapping
greaterOrEqualgteDirect mapping
lessThanltDirect mapping
lessOrEquallteDirect mapping
segmentMatchN/AMapped to SegmentKeys
before, afterlt, gtMapped to generic operators
semVerEqualeqMapped to basic equals
semVerGreaterThangtMapped to basic greater-than
semVerLessThanltMapped to basic less-than

Individual Targeting

LaunchDarkly individual targets (users/contexts) are mapped to targeting rules:

LD: Target["user-1", "user-2"] → Variation: 1
FS: Rule { condition: key in ["user-1", "user-2"], value: <variation value> }

Percentage Rollouts

LaunchDarkly's 0–100,000 weight scale is converted to FeatureSignals' basis points (0–10,000):

LD rollout weight: 50000 (50%)
FS percentage: 5000 (5000 basis points = 50%)

Post-Migration Verification

Verify Flag Counts

curl -X GET https://api.featuresignals.com/v1/projects/proj_abc123/flags \
-H "Authorization: Bearer YOUR_JWT"

Verify a Specific Flag's State

curl -X GET https://api.featuresignals.com/v1/projects/proj_abc123/flags/new-checkout \
-H "Authorization: Bearer YOUR_JWT"

Test Flag Evaluation

import { FeatureSignalsClient } from '@featuresignals/node';

const client = new FeatureSignalsClient('fs_srv_...', {
envKey: 'production',
baseURL: 'https://api.featuresignals.com',
});

await client.waitForReady();

// Compare with LaunchDarkly evaluation
const value = client.boolVariation('new-checkout', {
key: 'user-123',
attributes: { country: 'US', beta: true }
}, false);

console.log('Evaluated value:', value);

Known Limitations

  1. Segment imports: LaunchDarkly segments referenced via segmentMatch are mapped to SegmentKeys but the segment definitions themselves must be re-created manually in FeatureSignals.
  2. Prerequisite chains: Deeply nested prerequisite chains are flattened. Each flag lists its prerequisites but circular dependencies are not detected.
  3. Audit history: Change history from LaunchDarkly is not migrated. Audit logs start fresh in FeatureSignals.
  4. Custom roles & teams: Team member permissions and custom roles must be re-configured.
  5. Expired flags: Archived flags are skipped by default. If you need them, unarchive before migration.

Next Steps