Skip to content

Basic Usage

This guide covers the fundamental operations when working with Flaggy feature flags.

Creating Your First Feature Flag

Via Dashboard

  1. Navigate to https://localhost:5001/flaggy
  2. Click "Create Flag" or "New Flag"
  3. Fill in the flag details:
  4. Key - Unique identifier (e.g., "new-checkout-flow")
  5. Enabled - Toggle to enable/disable
  6. Value - Optional value (string, number, JSON, etc.)
  7. Description - Human-readable description
  8. Click "Save"

Programmatically

Create flags in your application code:

using Flaggy.Extensions;
using Flaggy.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(
        connectionString: connectionString,
        tableName: "feature_flags",        // optional
        userTableName: "users",            // optional
        autoMigrate: true                  // optional
    );
    options.UseMemoryCache(TimeSpan.FromMinutes(5));
});

var app = builder.Build();

// Create a simple flag
await app.InitializeFeatureFlagsAsync(async flagService =>
{
    await flagService.CreateFlagAsync(new FeatureFlag
    {
        Key = "new-checkout-flow",
        IsEnabled = false,
        Description = "New improved checkout experience"
    });
});

app.Run();

Checking if a Flag is Enabled

Simple Enabled/Disabled Check

Check if a feature flag is enabled in your application:

using Flaggy.Abstractions;

app.MapGet("/api/checkout", async (IFeatureFlagService flagService) =>
{
    var isNewCheckoutEnabled = await flagService.IsEnabledAsync("new-checkout-flow");

    if (isNewCheckoutEnabled)
    {
        return Results.Ok(new { message = "Using new checkout flow" });
    }

    return Results.Ok(new { message = "Using legacy checkout" });
});

In Controllers

Inject the flag service into your controllers:

using Flaggy.Abstractions;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IFeatureFlagService _flagService;

    public ProductsController(IFeatureFlagService flagService)
    {
        _flagService = flagService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await GetProductFromDb(id);

        var showReviews = await _flagService.IsEnabledAsync("product-reviews");

        if (showReviews)
        {
            product.Reviews = await GetReviews(id);
        }

        return Ok(product);
    }
}

In Services

Use flags in your business logic services:

using Flaggy.Abstractions;

public class OrderService
{
    private readonly IFeatureFlagService _flagService;
    private readonly IPaymentService _paymentService;

    public OrderService(IFeatureFlagService flagService, IPaymentService paymentService)
    {
        _flagService = flagService;
        _paymentService = paymentService;
    }

    public async Task<Order> CreateOrderAsync(Order order)
    {
        // Use legacy payment processor
        if (!await _flagService.IsEnabledAsync("new-payment-processor"))
        {
            return await _paymentService.ProcessWithLegacyAsync(order);
        }

        // Use new payment processor
        return await _paymentService.ProcessWithNewAsync(order);
    }
}

Getting Flag Values with Types

Feature flags can store typed values, not just boolean states.

String Values

Store and retrieve string values:

app.MapGet("/api/welcome", async (IFeatureFlagService flagService) =>
{
    // Get string value with default
    var message = await flagService.GetValueAsync(
        key: "welcome-message",
        defaultValue: "Welcome to our app!"
    );

    return Results.Ok(new { message });
});

Integer Values

Store and retrieve numeric configuration:

app.MapGet("/api/limits", async (IFeatureFlagService flagService) =>
{
    // Get integer value with default
    var maxUsers = await flagService.GetValueAsync<int>(
        key: "max-concurrent-users",
        defaultValue: 100
    );

    var maxRequests = await flagService.GetValueAsync<int>(
        key: "max-requests-per-minute",
        defaultValue: 60
    );

    return Results.Ok(new { maxUsers, maxRequests });
});

Double/Decimal Values

Store percentage-based values like discounts:

app.MapGet("/api/products/{id}/price", async (int id, IFeatureFlagService flagService) =>
{
    var product = await GetProduct(id);

    // Get discount rate with default
    var discountRate = await flagService.GetValueAsync<double>(
        key: "discount-rate",
        defaultValue: 0.0
    );

    var finalPrice = product.Price * (1 - discountRate);

    return Results.Ok(new {
        originalPrice = product.Price,
        discountRate,
        finalPrice
    });
});

Boolean Values

Store boolean configuration (note: different from IsEnabled):

app.MapGet("/api/features", async (IFeatureFlagService flagService) =>
{
    var enableAnalytics = await flagService.GetValueAsync<bool>(
        key: "enable-analytics",
        defaultValue: false
    );

    return Results.Ok(new { enableAnalytics });
});

JSON Values

Store complex JSON data:

app.MapGet("/api/config", async (IFeatureFlagService flagService) =>
{
    var configJson = await flagService.GetValueAsync(
        key: "theme-config",
        defaultValue: "{\"theme\": \"light\", \"fontSize\": 14}"
    );

    // Parse and use the JSON
    var config = JsonSerializer.Deserialize<ThemeConfig>(configJson);

    return Results.Ok(config);
});

How IsEnabled Works

Understanding the relationship between IsEnabled and Value:

Scenario IsEnabled GetValueAsync Result
Flag doesn't exist N/A Returns defaultValue
IsEnabled = false false Returns defaultValue
IsEnabled = true with value true Returns the stored value
IsEnabled = true no value true Returns null (or defaultValue if provided)
// Example: A flag with IsEnabled=false will always return the default
// even if it has a stored value
var result = await flagService.GetValueAsync<int>(
    key: "some-flag",
    defaultValue: 100
);
// Result: 100 (because IsEnabled is false)

// Example: A flag with IsEnabled=true returns its value
var result = await flagService.GetValueAsync<int>(
    key: "some-flag",
    defaultValue: 100
);
// Result: the actual value stored in the flag (e.g., 500)

Using Flags in Controllers and Services

Complete Controller Example

using Flaggy.Abstractions;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IFeatureFlagService _flagService;
    private readonly IUserService _userService;

    public UsersController(IFeatureFlagService flagService, IUserService userService)
    {
        _flagService = flagService;
        _userService = userService;
    }

    [HttpPost]
    public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest request)
    {
        // Check if new registration flow is enabled
        var useNewFlow = await _flagService.IsEnabledAsync("new-user-registration");

        if (useNewFlow)
        {
            return Ok(await _userService.CreateWithNewFlowAsync(request));
        }
        else
        {
            return Ok(await _userService.CreateWithLegacyFlowAsync(request));
        }
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(int id)
    {
        var user = await _userService.GetUserAsync(id);

        // Include premium features if flag is enabled
        var includePremiumFeatures = await _flagService.IsEnabledAsync("premium-features");

        if (includePremiumFeatures)
        {
            user.Features = await _userService.GetPremiumFeaturesAsync(id);
        }

        return Ok(user);
    }
}

Complete Service Example

using Flaggy.Abstractions;

public class EmailService
{
    private readonly IFeatureFlagService _flagService;
    private readonly IEmailProvider _emailProvider;

    public EmailService(IFeatureFlagService flagService, IEmailProvider emailProvider)
    {
        _flagService = flagService;
        _emailProvider = emailProvider;
    }

    public async Task SendWelcomeEmailAsync(User user)
    {
        // Get welcome email template from flag
        var template = await _flagService.GetValueAsync(
            key: "welcome-email-template",
            defaultValue: "default"
        );

        // Get email provider to use
        var provider = await _flagService.GetValueAsync(
            key: "email-provider",
            defaultValue: "sendgrid"
        );

        // Get sender email
        var senderEmail = await _flagService.GetValueAsync(
            key: "sender-email",
            defaultValue: "noreply@company.com"
        );

        await _emailProvider.SendAsync(new EmailMessage
        {
            To = user.Email,
            From = senderEmail,
            TemplateName = template,
            TemplateData = new { user.Name }
        });
    }
}

Managing Flags via Dashboard

Dashboard Features

The web UI allows you to:

  1. View All Flags - See a list of all configured flags
  2. Create Flags - Add new feature flags
  3. Edit Flags - Modify flag properties and values
  4. Enable/Disable - Toggle flags on/off with a single click
  5. Search - Find flags by key or description
  6. Manage Users - Create/delete dashboard users

Dashboard Workflow

1. Navigate to https://localhost:5001/flaggy
2. Log in with your credentials
3. View the flag list
4. Create new flags
5. Toggle flags on/off
6. Edit flag values
7. Changes take effect immediately (cache is invalidated)

Managing Flags Programmatically

Basic CRUD Operations

using Flaggy.Extensions;
using Flaggy.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFlaggy(options =>
{
    options.UseMySQL(connectionString: connectionString);
});

var app = builder.Build();

app.MapPost("/api/admin/flags", async (IFeatureFlagService flagService) =>
{
    // Create a new flag
    await flagService.CreateFlagAsync(new FeatureFlag
    {
        Key = "feature-x",
        IsEnabled = false,
        Value = "beta",
        Description = "Feature X in beta"
    });

    return Results.Ok();
});

app.MapPut("/api/admin/flags/{key}", async (string key, IFeatureFlagService flagService) =>
{
    // Get flag
    var flag = await flagService.GetFlagAsync(key);
    if (flag == null) return Results.NotFound();

    // Update flag
    flag.IsEnabled = true;
    await flagService.UpdateFlagAsync(flag);

    return Results.Ok();
});

app.MapDelete("/api/admin/flags/{key}", async (string key, IFeatureFlagService flagService) =>
{
    // Delete flag
    await flagService.DeleteFlagAsync(key);
    return Results.Ok();
});

Extension Methods

Use convenient extension methods for common operations:

using Flaggy.Extensions;

// Create only if doesn't exist
await flagService.CreateFlagIfNotExistsAsync("dark-mode", isEnabled: true);

// Upsert (create or update)
await flagService.UpsertFlagAsync("beta-features", isEnabled: false);

// Enable/Disable flags
await flagService.EnableFlagAsync("dark-mode");
await flagService.DisableFlagAsync("beta-features");

// Toggle flag
await flagService.ToggleFlagAsync("dark-mode");

// Update only value or description
await flagService.UpdateFlagValueAsync("theme", "auto");
await flagService.UpdateFlagDescriptionAsync("theme", "User theme preference");

// Check if flag exists
bool exists = await flagService.FlagExistsAsync("dark-mode");

// Get enabled/disabled flags
var enabledFlags = await flagService.GetEnabledFlagsAsync();
var disabledFlags = await flagService.GetDisabledFlagsAsync();

// Get summary
var summary = await flagService.GetFlagsSummaryAsync();
Console.WriteLine($"Total: {summary.TotalCount}, Enabled: {summary.EnabledCount}");

// Delete multiple flags
await flagService.DeleteFlagsAsync(new[] { "flag1", "flag2" });

// Delete all disabled flags
await flagService.DeleteAllDisabledFlagsAsync();

Fluent API Builder

Use the fluent API for cleaner flag creation:

using Flaggy.Helpers;

var initializer = new FeatureFlagInitializer(flagService);

await initializer.CreateFlag("premium-features")
    .Enabled()
    .WithDescription("Premium features for paid users")
    .WithValue("tier-1,tier-2")
    .CreateIfNotExistsAsync();

await initializer.CreateFlag("maintenance-mode")
    .Disabled()
    .WithDescription("Enable maintenance mode")
    .UpsertAsync();

Seeding Flags at Startup

Initialize your application with default flags:

using Flaggy.Extensions;
using Flaggy.Helpers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFlaggy(options =>
{
    options.UsePostgreSQL(connectionString: connectionString);
});

var app = builder.Build();

// Seed flags using extension method
await app.SeedFeatureFlagsAsync(
    new FeatureFlag { Key = "welcome-banner", IsEnabled = true },
    new FeatureFlag { Key = "new-dashboard", IsEnabled = false },
    new FeatureFlag { Key = "analytics", IsEnabled = true }
);

// Or seed using initializer for more control
await app.InitializeFeatureFlagsAsync(async flagService =>
{
    var initializer = new FeatureFlagInitializer(flagService);

    await initializer.CreateFlag("notifications")
        .Enabled()
        .WithDescription("Email notifications")
        .CreateIfNotExistsAsync();

    await initializer.CreateFlag("theme-selector")
        .Enabled()
        .WithValue("light")
        .WithDescription("Default theme")
        .CreateIfNotExistsAsync();
});

app.Run();

Simple Examples

Example 1: A/B Testing

app.MapGet("/api/landing", async (IFeatureFlagService flagService) =>
{
    var useNewDesign = await flagService.IsEnabledAsync("new-landing-design");

    if (useNewDesign)
    {
        return Results.Ok(new { design = "new", color = "blue" });
    }

    return Results.Ok(new { design = "legacy", color = "gray" });
});

Example 2: Feature Gradual Rollout

app.MapPost("/api/purchase", async (PurchaseRequest request, IFeatureFlagService flagService) =>
{
    var newCheckoutEnabled = await flagService.IsEnabledAsync("new-checkout-v2");

    if (newCheckoutEnabled)
    {
        // Process with new checkout system
        var result = await ProcessWithNewCheckoutAsync(request);
        return Results.Ok(result);
    }
    else
    {
        // Fall back to legacy checkout
        var result = await ProcessWithLegacyCheckoutAsync(request);
        return Results.Ok(result);
    }
});

Example 3: Configuration Management

public class AppSettings
{
    private readonly IFeatureFlagService _flagService;

    public AppSettings(IFeatureFlagService flagService)
    {
        _flagService = flagService;
    }

    public async Task<int> GetMaxRetries()
    {
        return await _flagService.GetValueAsync<int>("max-retries", defaultValue: 3);
    }

    public async Task<TimeSpan> GetRequestTimeout()
    {
        var seconds = await _flagService.GetValueAsync<int>(
            "request-timeout-seconds",
            defaultValue: 30
        );
        return TimeSpan.FromSeconds(seconds);
    }

    public async Task<string> GetApiEndpoint()
    {
        return await _flagService.GetValueAsync(
            "api-endpoint",
            defaultValue: "https://api.legacy.com"
        );
    }
}

// Usage
var settings = new AppSettings(flagService);
var endpoint = await settings.GetApiEndpoint();
var timeout = await settings.GetRequestTimeout();

Example 4: Caching Strategy

app.MapGet("/api/data", async (IFeatureFlagService flagService) =>
{
    var enableRedisCache = await flagService.IsEnabledAsync("redis-cache");
    var cacheDuration = await flagService.GetValueAsync<int>(
        "cache-duration-minutes",
        defaultValue: 5
    );

    var data = enableRedisCache
        ? await GetDataWithRedis(TimeSpan.FromMinutes(cacheDuration))
        : await GetDataWithMemoryCache(TimeSpan.FromMinutes(cacheDuration));

    return Results.Ok(data);
});

Next Steps