// 1 - LD User context
{
"kind": "user",
"key": "user-key-123abc",
"name": "Anna",
"email": "anna@globalhealthexample.com",
"organization": "Global Health Services",
"jobFunction": "doctor",
"device": "iPad"
}
// 1 - Statsig User object
{
"userID": "user-key-123abc",
"email": "anna@globalhealthexample.com",
"custom": {
"name": "Anna",
"organization": "Global Health Services",
"jobFunction": "doctor",
"device": "iPad"
}
}
```text
**Example 2: LD Multi context kind to Statsig User object conversion**
```javascript
// 2 - LD Multi context kind
{
"kind": "multi",
"user": {
"key": "user_abc",
"name": "Anna",
"email": "abc@company.com",
"region": "us-east"
},
"org": {
"key": "org_xyz",
"tier": "enterprise"
}
}
// 2 - Statsig user object
{
"userID": "user_abc",
"email": "abc@company.com",
"customIDs": {
"org_id": "org_xyz"
},
"custom": {
"user_name": "Anna",
"user_region": "us-east",
"org_tier": "enterprise"
}
}
```javascript
In Statsig, email is a top-level reserved field for the user object, so it should be placed directly as email (not user_email). Statsig expects fields like userID, email, ip, and userAgent at the top level for user targeting and analytics.
## Deciding what to migrate vs. not
Before you begin migrating flags from LaunchDarkly to Statsig, take this opportunity to audit your flags in the current system and do a cleanup. Many organizations accumulate flag debt over time - from stale flags, dead experiments, deprecated toggles, and legacy kill switches. Migration is a chance to start fresh with only what's valuable and active.
Utilize filters such as 'Lifecycle' and 'Type' in LaunchDarkly to determine which flags are worth importing into Statsig.
Below is a decision framework you can use to decide which flags to import into Statsig. Our migration script follows this framework by default but you can alter it if you need.
<Frame>
<img src="/images/tutorials/migration-launchdarkly/migration-decision-framework.png" alt="Migration Decision Framework" />
</Frame>
## Importing flags into Statsig
To import feature flags from LaunchDarkly to Statsig, you can use our official import tool which is designed for this specific purpose. The import tool fetches flags from LaunchDarkly, translates them into Statsig's format, and creates corresponding feature flags in Statsig. Additionally, it tracks the migration status and details in a CSV file.
There are two ways to invoke this tool:
1. **[Open source script](/guides/open-source-script) (Recommended)** - This is a good option if you want to customize the integration logic. It will also spit out a CSV of all of your LaunchDarkly flags, along with migration status and relevant URLs to the flag in LaunchDarkly and the gate in Statsig. This imports all of your environments.
2. **[Statsig console](/guides/ui-based-tool)** - UI-based wizard to help you import LaunchDarkly feature flags and segments into Statsig. It will tell you which gates and segments were migrated and which weren't. It only imports the "production" environment at the moment.
> 👉 If you are migrating from a different system, you will need to recreate flags manually in Statsig. Ideally you have done the cleaning in the previous step, so you will migrate a small number of flags. If you need assistance, please reach out over email or slack.
## Flipping evaluation from LaunchDarkly to Statsig
Once your flags have been imported into Statsig, the next step is to flip evaluation logic in your application. Instead of replacing every LaunchDarkly flag evaluation with Statsig calls, we recommend introducing a wrapper with gradual migration capabilities. This allows you to run both systems in parallel, compare outputs, and gradually switch over with minimal risk.
The wrapper approach provides several key benefits:
- **Parallel execution**: Run both systems simultaneously to validate behavior
- **Gradual rollout**: Migrate flags one at a time or by percentage
- **Easy rollback**: Quickly revert to LaunchDarkly if issues arise
- **Consistent interface**: Maintain existing application code structure
### Implementation Guide
**1. Before migration: LaunchDarkly Evaluation**
Here's what a typical LaunchDarkly setup might look like:
```javascript
import { LDClient } from 'launchdarkly-js-client-sdk';
const ldClient = LDClient.initialize('client-key', {
key: 'user_abc',
custom: { plan: 'pro' }
});
ldClient.on('ready', () => {
const isNewHomepageEnabled = ldClient.variation('new_homepage_flag', false);
const buttonColor = ldClient.variation('button_config', 'gray');
});
```text
**2. Create the Migration Wrapper**
Create a comprehensive wrapper (`featureWrapper.js`) that handles both systems. The wrapper should check Statsig first, and if unavailable, fallback to LaunchDarkly:
```javascript
import { getLDClient } from './launchdarklyService';
import { getStatsigClient } from './statsigService';
export const wrapperFlags = {
// For boolean flags
wrapperGetFlag(flagKey, defaultValue = false) {
const statsigClient = getStatsigClient();
if (statsigClient) {
return statsigClient.checkGate(flagKey);
}
const ldClient = getLDClient();
if (ldClient) {
return ldClient.variation(flagKey, defaultValue);
}
return defaultValue;
},
// For dynamic configs (string, number, or json flags)
wrapperGetConfig(user, configKey) {
const statsigClient = getStatsigClient();
if (statsigClient) {
const config = statsigClient.getConfig(user, configKey);
return {
get: (paramKey, defaultValue) => config.get(paramKey, defaultValue)
};
}
const ldClient = getLDClient();
if (ldClient) {
return {
get: (paramKey, defaultValue) => {
const ldKey = `${configKey}.${paramKey}`;
return ldClient.variation(ldKey, defaultValue);
}
};
}
return {
get: (_key, defaultValue) => defaultValue
};
}
};
```text
**3. Refactor application code to use the Wrapper**
Once the wrapper is in place, you'll want to route all flag checks through it. The application logic itself doesn't change, only the mechanism by which flags are retrieved.
```javascript
import { wrapperFlags } from './featureWrapper';
const user = {
userID: 'user_abc',
custom: { plan: 'pro' }
};
// Boolean gate
if (wrapperFlags.wrapperGetFlag('new_homepage_flag', user)) {
showNewHomepage();
}
// Multivariate config
const buttonConfig = wrapperFlags.wrapperGetConfig('button_config', user);
const buttonColor = buttonConfig.get('color', 'gray');