Skip to content

Migration Guide

Overview

This comprehensive guide helps teams migrate from other feature flag libraries to Flaggy. Whether you're coming from LaunchDarkly, Unleash, FeatureToggle, or custom implementations, this guide provides step-by-step instructions, code examples, and troubleshooting tips.

Why Migrate to Flaggy?

Key Advantages

  • Simple and Lightweight: No complex infrastructure or external services required
  • Multiple Storage Options: Choose from InMemory, MySQL, PostgreSQL, or MS SQL Server
  • Built-in Dashboard: Manage flags through a beautiful web UI
  • Zero Dependencies: Self-hosted solution with no vendor lock-in
  • Cost-Effective: No per-seat or usage-based pricing
  • Full Control: Your data stays in your infrastructure

When to Migrate

Consider migrating to Flaggy when:

  1. You want to reduce third-party service costs
  2. Your current solution is over-complicated for your needs
  3. You need full control over your feature flag infrastructure
  4. You want a self-hosted solution without external dependencies
  5. Your team needs a simpler, more maintainable approach

Pre-Migration Checklist

Before starting the migration, complete these preparatory steps:

1. Inventory Your Flags

// Document all existing flags
// Flag Name | Status | Dependencies | Owner | Purpose
// new-checkout | Enabled | Payment API | Team-A | New checkout flow
// dark-mode | Enabled | UI components | Team-B | Theme switcher
// beta-features | Disabled | Multiple | Team-C | Beta program

2. Identify Flag Dependencies

Map out which parts of your application depend on each flag:

// Example dependency mapping
/*
Flag: new-checkout
- Controllers: CheckoutController.cs
- Services: PaymentService.cs, OrderService.cs
- Views: Checkout.cshtml
- Tests: CheckoutTests.cs
*/

3. Plan Rollback Strategy

Ensure you have a rollback plan in case issues arise:

// Keep old library temporarily
// Use feature flag to switch between old and new implementations
if (await _flagService.IsEnabledAsync("use-flaggy-library"))
{
    // Use Flaggy
    isEnabled = await _flaggyService.IsEnabledAsync("feature-x");
}
else
{
    // Use old library as fallback
    isEnabled = await _oldService.IsEnabledAsync("feature-x");
}

4. Set Up Testing Environment

Create a test environment to validate the migration:

# Clone production database to test environment
# Install Flaggy in test environment
# Validate flag behavior matches production

Migration from LaunchDarkly

LaunchDarkly is a popular feature flag SaaS platform. Here's how to migrate to Flaggy.

Step 1: Export LaunchDarkly Flags

Export your flags using LaunchDarkly's API or dashboard:

// Using LaunchDarkly API to export flags
public async Task<List<LaunchDarklyFlag>> ExportLaunchDarklyFlags()
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("Authorization", "api-key-xxx");

    var response = await client.GetAsync("https://app.launchdarkly.com/api/v2/flags/your-project");
    var json = await response.Content.ReadAsStringAsync();

    return JsonSerializer.Deserialize<List<LaunchDarklyFlag>>(json);
}

Step 2: Install Flaggy

dotnet add package Flaggy
dotnet add package Flaggy.Provider.MySQL

Step 3: Configure Flaggy

using Flaggy.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Configure Flaggy with your preferred provider
builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection"),
        tableName: "feature_flags",        // optional
        userTableName: "users",            // optional
        autoMigrate: true                  // optional
    );
    options.UseMemoryCache(TimeSpan.FromMinutes(5));
});

var app = builder.Build();
app.Run();

Step 4: Transform and Import Flags

public async Task MigrateFromLaunchDarkly(IFeatureFlagService flaggyService)
{
    var ldFlags = await ExportLaunchDarklyFlags();

    foreach (var ldFlag in ldFlags)
    {
        // Map LaunchDarkly flag to Flaggy
        await flaggyService.CreateFlagAsync(new FeatureFlag
        {
            Key = ldFlag.Key,
            IsEnabled = ldFlag.On, // LaunchDarkly uses 'on' property
            Value = ldFlag.Variations?.FirstOrDefault()?.Value?.ToString(),
            Description = $"{ldFlag.Name} - {ldFlag.Description}"
        });
    }
}

Step 5: Update Code References

// Before: LaunchDarkly
using LaunchDarkly.Sdk.Server;

public class OrderController : ControllerBase
{
    private readonly ILdClient _ldClient;

    public async Task<IActionResult> Process()
    {
        var user = LaunchDarkly.Sdk.User.WithKey(_currentUser.Id);
        var isEnabled = await _ldClient.BoolVariationAsync("new-checkout", user, false);

        return isEnabled ? NewCheckout() : OldCheckout();
    }
}

// After: Flaggy
using Flaggy.Abstractions;

public class OrderController : ControllerBase
{
    private readonly IFeatureFlagService _flagService;

    public async Task<IActionResult> Process()
    {
        var isEnabled = await _flagService.IsEnabledAsync("new-checkout");

        return isEnabled ? NewCheckout() : OldCheckout();
    }
}

Step 6: Handle User Targeting (Optional)

LaunchDarkly supports user targeting. In Flaggy, you can implement this with flag values:

// LaunchDarkly targeting logic
// Before:
var user = LaunchDarkly.Sdk.User.WithKey(userId).AndEmail(email);
var isEnabled = await _ldClient.BoolVariationAsync("beta-feature", user, false);

// After: Flaggy with custom targeting
var betaUsers = await _flagService.GetValueAsync<string>("beta-users", "");
var betaUserList = betaUsers.Split(',');
var isEnabled = betaUserList.Contains(userId) ||
                await _flagService.IsEnabledAsync("beta-feature");

Migration Checklist

  • Export all flags from LaunchDarkly
  • Install Flaggy packages
  • Configure Flaggy services
  • Import flags to Flaggy
  • Update code references from ILdClient to IFeatureFlagService
  • Test all flag evaluations
  • Update CI/CD pipelines
  • Remove LaunchDarkly SDK
  • Cancel LaunchDarkly subscription

Migration from Unleash

Unleash is an open-source feature toggle system. Here's the migration path.

Step 1: Export Unleash Flags

public async Task<List<UnleashFeature>> ExportUnleashFlags()
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("Authorization", "unleash-api-key");

    var response = await client.GetAsync("https://your-unleash-instance.com/api/admin/features");
    var json = await response.Content.ReadAsStringAsync();

    return JsonSerializer.Deserialize<List<UnleashFeature>>(json);
}

Step 2: Install and Configure Flaggy

using Flaggy.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Flaggy with PostgreSQL (similar to Unleash)
builder.Services.AddFlaggy(options =>
{
    options.UsePostgreSQL(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection"),
        tableName: "feature_flags",        // optional
        userTableName: "users",            // optional
        autoMigrate: true                  // optional
    );
});

var app = builder.Build();
app.Run();

Step 3: Migrate Flags

public async Task MigrateFromUnleash(IFeatureFlagService flaggyService)
{
    var unleashFlags = await ExportUnleashFlags();

    foreach (var unleashFlag in unleashFlags)
    {
        await flaggyService.CreateFlagAsync(new FeatureFlag
        {
            Key = unleashFlag.Name,
            IsEnabled = unleashFlag.Enabled,
            Value = unleashFlag.Variants?.FirstOrDefault()?.Name,
            Description = unleashFlag.Description
        });
    }
}

Step 4: Update Code

// Before: Unleash
using Unleash;

public class FeatureService
{
    private readonly IUnleash _unleash;

    public bool IsFeatureEnabled(string feature)
    {
        return _unleash.IsEnabled(feature);
    }
}

// After: Flaggy
using Flaggy.Abstractions;

public class FeatureService
{
    private readonly IFeatureFlagService _flagService;

    public async Task<bool> IsFeatureEnabled(string feature)
    {
        return await _flagService.IsEnabledAsync(feature);
    }
}

Migration Checklist

  • Export flags from Unleash
  • Install Flaggy packages
  • Configure Flaggy with PostgreSQL provider
  • Import flags to Flaggy
  • Update code from IUnleash to IFeatureFlagService
  • Test flag evaluations
  • Migrate activation strategies (if applicable)
  • Update deployment scripts
  • Decommission Unleash instance

Migration from FeatureToggle Library

If you're using the FeatureToggle library for .NET, here's how to migrate.

Step 1: Identify Current Toggles

// Before: FeatureToggle
using FeatureToggle;

public class MyFeature : SimpleFeatureToggle { }

public class MyController : ControllerBase
{
    private readonly MyFeature _myFeature;

    public IActionResult Index()
    {
        if (_myFeature.FeatureEnabled)
        {
            return NewFeature();
        }
        return OldFeature();
    }
}

Step 2: Install Flaggy

dotnet add package Flaggy
dotnet add package Flaggy.Provider.MySQL

Step 3: Configure Flaggy

using Flaggy.Extensions;

builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection")
    );
});

Step 4: Update Code

// After: Flaggy
using Flaggy.Abstractions;

public class MyController : ControllerBase
{
    private readonly IFeatureFlagService _flagService;

    public async Task<IActionResult> Index()
    {
        if (await _flagService.IsEnabledAsync("my-feature"))
        {
            return NewFeature();
        }
        return OldFeature();
    }
}

Step 5: Seed Initial Flags

// Create flags based on your FeatureToggle configuration
var app = builder.Build();

await app.SeedFeatureFlagsAsync(
    new FeatureFlag { Key = "my-feature", IsEnabled = true },
    new FeatureFlag { Key = "beta-feature", IsEnabled = false },
    new FeatureFlag { Key = "new-ui", IsEnabled = true }
);

app.Run();

Migration Checklist

  • Document all FeatureToggle classes
  • Install Flaggy packages
  • Configure Flaggy services
  • Create corresponding flags in Flaggy
  • Replace toggle classes with Flaggy service calls
  • Update dependency injection
  • Test all toggle points
  • Remove FeatureToggle library

Migration from Custom Implementation

Many teams have custom feature flag implementations. Here's how to migrate.

Common Custom Patterns

Pattern 1: Configuration-Based Flags

// Before: appsettings.json
{
  "FeatureFlags": {
    "NewCheckout": true,
    "BetaMode": false
  }
}

// Code
public class FeatureFlagService
{
    private readonly IConfiguration _config;

    public bool IsEnabled(string key)
    {
        return _config.GetValue<bool>($"FeatureFlags:{key}");
    }
}

// After: Flaggy
using Flaggy.Abstractions;

public class FeatureFlagService
{
    private readonly IFeatureFlagService _flagService;

    public async Task<bool> IsEnabled(string key)
    {
        return await _flagService.IsEnabledAsync(key);
    }
}

Pattern 2: Database-Based Flags

// Before: Custom database implementation
public class CustomFlagService
{
    private readonly DbContext _db;

    public async Task<bool> IsEnabled(string key)
    {
        var flag = await _db.Flags.FindAsync(key);
        return flag?.IsEnabled ?? false;
    }
}

// After: Flaggy (handles database operations internally)
using Flaggy.Abstractions;

public class FeatureFlagService
{
    private readonly IFeatureFlagService _flagService;

    public async Task<bool> IsEnabled(string key)
    {
        return await _flagService.IsEnabledAsync(key);
    }
}

Pattern 3: Environment Variable Flags

// Before: Environment variables
public class FeatureFlagService
{
    public bool IsEnabled(string key)
    {
        var value = Environment.GetEnvironmentVariable($"FEATURE_{key}");
        return bool.TryParse(value, out var result) && result;
    }
}

// After: Flaggy with environment-based initialization
var app = builder.Build();

// Seed from environment variables at startup
await app.InitializeFeatureFlagsAsync(async flagService =>
{
    await SeedFromEnvironmentVariables(flagService);
});

public async Task SeedFromEnvironmentVariables(IFeatureFlagService flagService)
{
    var envVars = Environment.GetEnvironmentVariables();

    foreach (DictionaryEntry env in envVars)
    {
        var key = env.Key.ToString();
        if (key.StartsWith("FEATURE_"))
        {
            var flagKey = key.Replace("FEATURE_", "").ToLower().Replace("_", "-");
            var isEnabled = bool.TryParse(env.Value?.ToString(), out var result) && result;

            await flagService.UpsertFlagAsync(flagKey, isEnabled);
        }
    }
}

Migration Strategy

Step 1: Analyze Current Implementation

Document your current implementation:

// Document each flag
/*
Flag: new-checkout
Storage: appsettings.json
Default: false
Used In: CheckoutController.cs, OrderService.cs
Owner: team-payments
*/

Step 2: Create Migration Script

public class FlagMigrationService
{
    private readonly IConfiguration _config;
    private readonly IFeatureFlagService _flagService;

    public async Task MigrateCustomFlags()
    {
        // From configuration
        var configFlags = _config.GetSection("FeatureFlags")
            .GetChildren()
            .ToDictionary(x => x.Key, x => x.Get<bool>());

        foreach (var flag in configFlags)
        {
            await _flagService.CreateFlagAsync(new FeatureFlag
            {
                Key = ConvertToKebabCase(flag.Key),
                IsEnabled = flag.Value,
                Description = $"Migrated from appsettings.json"
            });
        }

        // From database
        var dbFlags = await _db.CustomFlags.ToListAsync();

        foreach (var flag in dbFlags)
        {
            await _flagService.CreateFlagAsync(new FeatureFlag
            {
                Key = flag.Name,
                IsEnabled = flag.Active,
                Value = flag.ConfigValue,
                Description = flag.Notes
            });
        }
    }

    private string ConvertToKebabCase(string input)
    {
        return Regex.Replace(input, "(?<!^)([A-Z])", "-$1").ToLower();
    }
}

Step 3: Implement Adapter Pattern (Temporary)

During migration, use an adapter to support both systems:

public class FeatureFlagAdapter
{
    private readonly IFeatureFlagService _flaggy;
    private readonly IConfiguration _config;
    private readonly bool _useFlaggy;

    public FeatureFlagAdapter(
        IFeatureFlagService flaggy,
        IConfiguration config)
    {
        _flaggy = flaggy;
        _config = config;
        _useFlaggy = config.GetValue<bool>("UseFlaggy", false);
    }

    public async Task<bool> IsEnabled(string key)
    {
        if (_useFlaggy)
        {
            return await _flaggy.IsEnabledAsync(key);
        }

        // Fallback to old implementation
        return _config.GetValue<bool>($"FeatureFlags:{key}", false);
    }
}

Step 4: Update Code Gradually

// Phase 1: Replace with adapter
services.AddScoped<FeatureFlagAdapter>();

// Phase 2: Test thoroughly
// Phase 3: Switch to Flaggy directly
services.AddFlaggy(options =>
{
    options.UseMySQL(connectionString: connectionString);
});

// Phase 4: Remove old implementation

Migration Checklist

  • Document current custom implementation
  • Create migration script for flag data
  • Install Flaggy packages
  • Implement adapter pattern for gradual migration
  • Migrate flags to Flaggy
  • Update code references gradually
  • Test each migration phase
  • Remove adapter and old code
  • Clean up old flag storage (config, database tables, etc.)

Post-Migration Tasks

After completing the migration, perform these tasks:

1. Verify All Flags

public async Task VerifyMigration(IFeatureFlagService flagService)
{
    var allFlags = await flagService.GetAllFlagsAsync();

    Console.WriteLine($"Total flags migrated: {allFlags.Count()}");

    foreach (var flag in allFlags)
    {
        Console.WriteLine($"✓ {flag.Key}: {flag.IsEnabled}");
    }
}

2. Update Documentation

# Feature Flags Documentation

## Flaggy Configuration

- **Provider**: MySQL
- **Cache**: Memory Cache (5 min TTL)
- **Dashboard**: https://app.company.com/flaggy

## Flag List

| Flag Key | Status | Purpose | Owner |
|----------|--------|---------|-------|
| new-checkout | Enabled | New checkout flow | team-payments |
| dark-mode | Enabled | Theme switcher | team-ui |

3. Train Team Members

# Flaggy Quick Start Guide

## Checking a Flag
```csharp
var isEnabled = await _flagService.IsEnabledAsync("feature-key");

Creating a Flag

await _flagService.CreateFlagAsync(new FeatureFlag
{
    Key = "my-feature",
    IsEnabled = true,
    Description = "Description here"
});

Dashboard Access

Visit: https://app.company.com/flaggy

### 4. Set Up Monitoring

```csharp
// Add health checks
builder.Services.AddHealthChecks()
    .AddCheck<FeatureFlagHealthCheck>("feature_flags");

// Log flag operations
public class MonitoredFeatureFlagService : IFeatureFlagService
{
    private readonly IFeatureFlagService _inner;
    private readonly ILogger _logger;

    public async Task<bool> IsEnabledAsync(string key, CancellationToken ct = default)
    {
        var result = await _inner.IsEnabledAsync(key, ct);
        _logger.LogInformation("Flag {Key} evaluated: {Result}", key, result);
        return result;
    }
}

5. Clean Up Old Dependencies

# Remove old feature flag packages
dotnet remove package LaunchDarkly.ServerSdk
# or
dotnet remove package Unleash.Client
# or
dotnet remove package FeatureToggle

# Update project files
# Remove old configuration sections
# Delete migration adapter code

Troubleshooting

Issue: Flags Not Evaluating Correctly

Symptoms: Flags return unexpected values after migration

Solutions:

// 1. Verify flag was created correctly
var flag = await _flagService.GetFlagAsync("feature-key");
Console.WriteLine($"Key: {flag?.Key}, Enabled: {flag?.IsEnabled}");

// 2. Check for naming mismatches
// Old: "NewCheckout"
// New: "new-checkout" (kebab-case)

// 3. Clear cache if stale
await _flagService.RefreshCacheAsync();

Issue: Performance Degradation

Symptoms: Application slower after migration

Solutions:

// 1. Enable caching
builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(connectionString: connectionString);
    options.UseMemoryCache(TimeSpan.FromMinutes(5)); // Increase cache time
});

// 2. Use distributed cache for multi-server
builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(connectionString: connectionString);
    options.UseRedisCache("localhost:6379", TimeSpan.FromMinutes(5));
});

// 3. Check flag usage patterns
// Avoid checking flags in tight loops

Issue: Database Connection Errors

Symptoms: Cannot connect to database provider

Solutions:

// 1. Verify connection string
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
Console.WriteLine($"Connection: {connectionString}");

// 2. Test database connectivity
using (var connection = new MySqlConnection(connectionString))
{
    await connection.OpenAsync();
    Console.WriteLine("Database connected successfully");
}

// 3. Check auto-migration status
builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(
        connectionString: connectionString,
        autoMigrate: true // Ensure tables are created
    );
});

Issue: Missing Flags After Migration

Symptoms: Some flags from old system are missing

Solutions:

// 1. Verify migration script ran completely
public async Task VerifyAllFlagsMigrated(
    List<string> oldFlagKeys,
    IFeatureFlagService flagService)
{
    var newFlags = await flagService.GetAllFlagsAsync();
    var newFlagKeys = newFlags.Select(f => f.Key).ToHashSet();

    var missing = oldFlagKeys.Where(k => !newFlagKeys.Contains(k)).ToList();

    if (missing.Any())
    {
        Console.WriteLine($"Missing flags: {string.Join(", ", missing)}");
        // Re-run migration for missing flags
    }
}

// 2. Check for naming transformation errors
// 3. Manually create missing flags

Migration Timeline Example

Week 1: Preparation

  • Day 1-2: Document all existing flags
  • Day 3: Set up test environment with Flaggy
  • Day 4-5: Create and test migration scripts

Week 2: Implementation

  • Day 1-2: Migrate flags to test environment
  • Day 3-4: Update code in test branches
  • Day 5: Integration testing

Week 3: Deployment

  • Day 1: Deploy to staging environment
  • Day 2-3: Staging validation
  • Day 4: Production deployment (with rollback plan)
  • Day 5: Monitor and validate

Week 4: Cleanup

  • Day 1-2: Remove old library code
  • Day 3: Update documentation
  • Day 4: Train team
  • Day 5: Retrospective and lessons learned

Getting Help

If you encounter issues during migration:

  1. Check Documentation: Review Flaggy docs
  2. GitHub Issues: Search or create an issue
  3. Community Support: Ask in discussions
  4. Professional Support: Contact for enterprise migration assistance

Next Steps

After migrating to Flaggy: