Flaggy 0.1.0
dotnet add package Flaggy --version 0.1.0
NuGet\Install-Package Flaggy -Version 0.1.0
<PackageReference Include="Flaggy" Version="0.1.0" />
<PackageVersion Include="Flaggy" Version="0.1.0" />
<PackageReference Include="Flaggy" />
paket add Flaggy --version 0.1.0
#r "nuget: Flaggy, 0.1.0"
#:package Flaggy@0.1.0
#addin nuget:?package=Flaggy&version=0.1.0
#tool nuget:?package=Flaggy&version=0.1.0
<p align="center"> <img src="flaggy.ico" alt="Flaggy Logo" width="128" height="128"> </p>
<h1 align="center">Flaggy</h1>
<p align="center">A simple, fast, and modular feature flag library for .NET</p>
<p align="center"> <a href="#features">Features</a> • <a href="#quick-start">Quick Start</a> • <a href="#packages">Packages</a> • <a href="#provider-configuration">Providers</a> • <a href="#database-schema">Schema</a> • <a href="#contributing">Contributing</a> </p>
Features
- Simple & Fast - Minimal overhead with efficient caching
- Flexible Caching - Support for MemoryCache (default) and Redis with automatic cache invalidation
- Modular Design - Use only what you need with separate provider packages
- Multiple Providers - MySQL, PostgreSQL, MS SQL Server, and InMemory (with JSON persistence) providers included
- Auto-Migration - Automatic database schema creation and version management
- Flag Values - Store typed values (string, int, double, etc.) in flags, not just on/off states
- Beautiful Dashboard - Web UI to manage feature flags with database-backed user authentication
- User Management - Built-in user management with BCrypt password hashing and secure authentication
- Programmatic API - Full CRUD operations with extension methods and fluent API for managing flags without UI
- Easy Integration - Simple configuration with dependency injection
- Production Ready - Built with best practices and performance in mind
Packages
| Package | Description | NuGet |
|---|---|---|
Flaggy |
Core library with abstractions, services, MemoryCache, Redis, and UI dashboard | - |
Flaggy.Provider.MySQL |
MySQL storage provider with auto-migration | - |
Flaggy.Provider.PostgreSQL |
PostgreSQL storage provider with auto-migration | - |
Flaggy.Provider.MsSql |
Microsoft SQL Server storage provider with auto-migration | - |
Quick Start
1. Install Packages
# Install core library with a provider
dotnet add package Flaggy
dotnet add package Flaggy.Provider.MySQL
# Optional: Add UI dashboard
dotnet add package Flaggy.UI
2. Configure Services
using Flaggy.Extensions;
using Flaggy.UI.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add Flaggy with MySQL provider (auto-migration enabled by default)
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: "Server=localhost;Database=myapp;User=root;Password=pass;",
tableName: "feature_flags", // optional, default is "feature_flags"
autoMigrate: true); // optional, default is true - automatically creates tables
options.UseMemoryCache(cacheExpiration: TimeSpan.FromMinutes(5));
});
var app = builder.Build();
// Add Flaggy UI Dashboard (optional)
app.UseFlaggyUI(options =>
{
options.RoutePrefix = "/flaggy"; // default is "/flaggy"
options.RequireAuthorization = false; // optional
});
app.Run();
3. Use Feature Flags
Simple Enabled/Disabled Check
app.MapGet("/api/products", async (IFeatureFlagService flagService) =>
{
var isNewUIEnabled = await flagService.IsEnabledAsync("new-product-ui");
if (isNewUIEnabled)
{
return Results.Ok(new { message = "New UI is enabled!" });
}
return Results.Ok(new { message = "Using legacy UI" });
});
Using Flag Values
Flags can have values (string, int, double, etc.). The IsEnabled property controls whether the flag is published/active.
You can optionally provide a default value that will be returned if the flag doesn't exist or is disabled.
// String value with default
app.MapGet("/welcome", async (IFeatureFlagService flagService) =>
{
// Returns "Welcome!" if flag doesn't exist or is disabled
var message = await flagService.GetValueAsync("welcome-message", defaultValue: "Welcome!");
return Results.Ok(new { message });
});
// Integer value with default
app.MapGet("/users/max-limit", async (IFeatureFlagService flagService) =>
{
// Returns 100 if flag doesn't exist or is disabled
var maxUsers = await flagService.GetValueAsync<int>("max-users", defaultValue: 100);
return Results.Ok(new { limit = maxUsers });
});
// Double value (discount rate) with default
app.MapGet("/products/price", async (IFeatureFlagService flagService) =>
{
// Returns 0.0 (no discount) if flag doesn't exist or is disabled
var discountRate = await flagService.GetValueAsync<double>("discount-rate", defaultValue: 0.0);
var originalPrice = 100.0;
var finalPrice = originalPrice * (1 - discountRate.Value);
return Results.Ok(new { originalPrice, finalPrice });
});
How it works:
- Enabled = false: Flag is defined but not active.
GetValueAsyncreturns thedefaultValue(ornullif no default provided). - Enabled = true: Flag is active.
GetValueAsyncreturns the flag's value. - Flag doesn't exist:
GetValueAsyncreturns thedefaultValue(ornullif no default provided). - Value: Can be any string. Use
GetValueAsync<T>()for type conversion (int, double, bool, etc.).
4. Access Dashboard
Navigate to https://localhost:5001/flaggy to manage your feature flags through the web UI.
Provider Configuration
InMemory Provider (Development)
The InMemory provider persists flags to a JSON file for durability across restarts.
using Flaggy.Extensions;
using Flaggy.Providers;
// Default: saves to "flaggy-flags.json" in current directory
builder.Services.AddFlaggy(options =>
{
options.UseInMemory();
options.UseMemoryCache(TimeSpan.FromMinutes(5));
});
MySQL Provider
MySQL provider includes automatic migration with version tracking. No manual schema setup required!
using Flaggy.Extensions;
// Auto-migration enabled by default
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: "Server=localhost;Database=myapp;User=root;Password=pass;",
tableName: "feature_flags", // optional, default is "feature_flags"
userTableName: "users", // optional, default is "users"
autoMigrate: true); // optional, default is true
options.UseMemoryCache(cacheExpiration: TimeSpan.FromMinutes(5));
});
// Tables will be created automatically:
// - feature_flags: stores your feature flags
// - users: stores dashboard users with BCrypt hashed passwords
// - flaggy_migrations: tracks schema version
PostgreSQL Provider
PostgreSQL provider also includes automatic migration with version tracking.
using Flaggy.Extensions;
// Auto-migration enabled by default
builder.Services.AddFlaggy(options =>
{
options.UsePostgreSQL(
connectionString: "Host=localhost;Database=myapp;Username=postgres;Password=pass",
tableName: "feature_flags", // optional, default is "feature_flags"
userTableName: "users", // optional, default is "users"
autoMigrate: true); // optional, default is true
options.UseMemoryCache(cacheExpiration: TimeSpan.FromMinutes(5));
});
// Tables will be created automatically:
// - feature_flags: stores your feature flags
// - users: stores dashboard users with BCrypt hashed passwords
// - flaggy_migrations: tracks schema version
MS SQL Server Provider
MS SQL Server provider includes automatic migration with version tracking.
using Flaggy.Extensions;
// Auto-migration enabled by default
builder.Services.AddFlaggy(options =>
{
options.UseMsSql(
connectionString: "Server=localhost;Database=myapp;User Id=sa;Password=pass;TrustServerCertificate=True",
tableName: "feature_flags", // optional, default is "feature_flags"
userTableName: "users", // optional, default is "users"
autoMigrate: true); // optional, default is true
options.UseMemoryCache(cacheExpiration: TimeSpan.FromMinutes(5));
});
// Tables will be created automatically:
// - feature_flags: stores your feature flags
// - users: stores dashboard users with BCrypt hashed passwords
// - flaggy_migrations: tracks schema version
Programmatic Flag Management (Without UI)
You can manage feature flags programmatically without using the web UI. Flaggy provides extension methods and helpers for easy flag management.
Basic Operations
public class MyService
{
private readonly IFeatureFlagService _flagService;
public MyService(IFeatureFlagService flagService)
{
_flagService = flagService;
}
public async Task ManageFlags()
{
// Create a new flag
await _flagService.CreateFlagAsync(new FeatureFlag
{
Key = "new-feature",
IsEnabled = false,
Value = "beta",
Description = "New feature in beta"
});
// List all flags
var allFlags = await _flagService.GetAllFlagsAsync();
// Get specific flag
var flag = await _flagService.GetFlagAsync("new-feature");
// Update flag
flag.IsEnabled = true;
await _flagService.UpdateFlagAsync(flag);
// Delete flag
await _flagService.DeleteFlagAsync("new-feature");
}
}
Extension Methods
Flaggy provides 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", "Auto theme detection");
// 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
Seed default flags when your application starts:
using Flaggy.Extensions;
using Flaggy.Helpers;
var builder = WebApplication.CreateBuilder();
builder.Services.AddFlaggy(options =>
{
options.UsePostgreSQL("Host=localhost;...");
options.UseMemoryCache(TimeSpan.FromMinutes(5));
});
var app = builder.Build();
// Method 1: Using extension method
await app.SeedFeatureFlagsAsync(
new FeatureFlag { Key = "welcome-banner", IsEnabled = true },
new FeatureFlag { Key = "new-dashboard", IsEnabled = false }
);
// Method 2: Using initializer
await app.InitializeFeatureFlagsAsync(async flagService =>
{
var initializer = new FeatureFlagInitializer(flagService);
await initializer.CreateFlag("notifications")
.Enabled()
.WithDescription("Email notifications")
.CreateIfNotExistsAsync();
});
app.Run();
Console Application Example
using Flaggy.Extensions;
using Flaggy.Providers;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddFlaggy(new InMemoryFeatureFlagProvider());
var serviceProvider = services.BuildServiceProvider();
var flagService = serviceProvider.GetRequiredService<IFeatureFlagService>();
// Create flags
await flagService.CreateFlagAsync(new FeatureFlag
{
Key = "feature-1",
IsEnabled = true,
Description = "First feature"
});
// List all flags
var flags = await flagService.GetAllFlagsAsync();
foreach (var flag in flags)
{
Console.WriteLine($"{flag.Key}: {(flag.IsEnabled ? "✓" : "✗")}");
}
Complete Examples
For more examples, see ProgrammaticUsageExamples.cs which includes:
- Basic CRUD operations
- Extension method usage
- Fluent builder API
- Seeding and initialization
- Console application examples
- Controller/Service integration patterns
Advanced Usage
Custom Authorization for Dashboard
app.UseFlaggyUI(options =>
{
options.RoutePrefix = "/admin/flags";
options.RequireAuthorization = true;
options.AuthorizationFilter = context =>
{
// Custom authorization logic
return context.User.IsInRole("Admin");
};
});
Manual Cache Refresh
app.MapPost("/api/flags/refresh", async (IFeatureFlagService flagService) =>
{
await flagService.RefreshCacheAsync();
return Results.Ok(new { message = "Cache refreshed" });
});
Get Flag Details
app.MapGet("/api/flags/{key}", async (string key, IFeatureFlagService flagService) =>
{
var flag = await flagService.GetFlagAsync(key);
if (flag == null)
{
return Results.NotFound();
}
return Results.Ok(flag);
});
Get All Flags
app.MapGet("/api/flags", async (IFeatureFlagService flagService) =>
{
var flags = await flagService.GetAllFlagsAsync();
return Results.Ok(flags);
});
Custom Provider
Create your own provider by implementing IFeatureFlagProvider:
using Flaggy.Abstractions;
using Flaggy.Models;
public class CustomFeatureFlagProvider : IFeatureFlagProvider
{
public async Task<FeatureFlag?> GetFlagAsync(string key, CancellationToken cancellationToken = default)
{
// Your implementation
}
public async Task<IEnumerable<FeatureFlag>> GetAllFlagsAsync(CancellationToken cancellationToken = default)
{
// Your implementation
}
public async Task<bool> CreateFlagAsync(FeatureFlag flag, CancellationToken cancellationToken = default)
{
// Your implementation
}
public async Task<bool> UpdateFlagAsync(FeatureFlag flag, CancellationToken cancellationToken = default)
{
// Your implementation
}
public async Task<bool> DeleteFlagAsync(string key, CancellationToken cancellationToken = default)
{
// Your implementation
}
}
Then use it:
builder.Services.AddFlaggy(new CustomFeatureFlagProvider());
Database Schema
Note: These tables are created automatically by the migration system. You don't need to run these scripts manually!
MySQL
-- Feature flags table (auto-created by migrations)
CREATE TABLE feature_flags (
`Key` VARCHAR(255) PRIMARY KEY,
IsEnabled BOOLEAN NOT NULL DEFAULT FALSE,
`Value` TEXT NULL, -- Added in Version 2
Description TEXT NULL,
CreatedAt DATETIME NULL,
UpdatedAt DATETIME NULL
);
-- Migration version tracking table (auto-created)
CREATE TABLE flaggy_migrations (
Id INT AUTO_INCREMENT PRIMARY KEY,
Version INT NOT NULL UNIQUE,
Description VARCHAR(500) NOT NULL,
AppliedAt DATETIME NOT NULL
);
PostgreSQL
-- Feature flags table (auto-created by migrations)
CREATE TABLE feature_flags (
key VARCHAR(255) PRIMARY KEY,
is_enabled BOOLEAN NOT NULL DEFAULT FALSE,
value TEXT NULL, -- Added in Version 2
description TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
-- Migration version tracking table (auto-created)
CREATE TABLE flaggy_migrations (
id SERIAL PRIMARY KEY,
version INT NOT NULL UNIQUE,
description VARCHAR(500) NOT NULL,
applied_at TIMESTAMP NOT NULL
);
Migration System
Flaggy includes a built-in migration system with version tracking. When you provide a connection string:
- Version Table Creation: On first run,
flaggy_migrationstable is created automatically - Version Check: Checks current database version against available migrations
- Auto-Upgrade: Runs pending migrations in order (Version 1, 2, 3, etc.)
- Version Tracking: Each migration is recorded with version, description, and timestamp
Disable Auto-Migration
If you prefer manual control:
using Flaggy.Extensions;
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: "...",
autoMigrate: false); // Disable auto-migration
options.UseMemoryCache(TimeSpan.FromMinutes(5));
});
Migration History
The migration system automatically applies schema changes as new versions are released:
- Version 1: Initial table creation with Key, IsEnabled, Description, CreatedAt, UpdatedAt
- Version 2: Added Value column for storing flag values (current)
Future migrations will be applied automatically when you upgrade to newer versions. The system ensures migrations run only once and in the correct order.
Caching
Flaggy provides flexible caching strategies to optimize performance:
Memory Cache (Default)
By default, Flaggy uses IMemoryCache for caching:
using Flaggy.Enums;
using Flaggy.Extensions;
// Default: MemoryCache with 5 minutes expiration
builder.Services.AddFlaggy(new InMemoryFeatureFlagProvider());
// Custom cache expiration
builder.Services.AddFlaggy(
provider: new InMemoryFeatureFlagProvider(),
cachingProvider: CachingProvider.Memory,
cacheExpiration: TimeSpan.FromMinutes(10)
);
Redis Cache
For distributed scenarios, use Redis caching:
dotnet add package Flaggy.Caching.Redis
using Flaggy.Enums;
using Flaggy.Extensions;
// Simple Redis configuration (no authentication)
builder.Services.AddFlaggy(
provider: new InMemoryFeatureFlagProvider(),
cachingProvider: CachingProvider.Redis,
cacheExpiration: TimeSpan.FromMinutes(10),
redisConnectionString: "localhost:6379",
redisDatabase: 0 // optional, default is 0
);
// With password authentication
builder.Services.AddFlaggy(
provider: new InMemoryFeatureFlagProvider(),
cachingProvider: CachingProvider.Redis,
redisConnectionString: "localhost:6379,password=mypassword",
cacheExpiration: TimeSpan.FromMinutes(10)
);
// With username and password (Redis 6+)
builder.Services.AddFlaggy(
provider: new InMemoryFeatureFlagProvider(),
cachingProvider: CachingProvider.Redis,
redisConnectionString: "localhost:6379,user=myuser,password=mypassword",
cacheExpiration: TimeSpan.FromMinutes(10)
);
// With SSL/TLS
builder.Services.AddFlaggy(
provider: new InMemoryFeatureFlagProvider(),
cachingProvider: CachingProvider.Redis,
redisConnectionString: "localhost:6380,ssl=true,password=mypassword"
);
// With MySQL provider
builder.Services.AddFlaggyMySql(
connectionString: "Server=localhost;Database=myapp;User=root;Password=pass;"
);
builder.Services.AddFlaggy(
provider: sp.GetRequiredService<IFeatureFlagProvider>(),
cachingProvider: CachingProvider.Redis,
redisConnectionString: "localhost:6379,password=mypassword",
cacheExpiration: TimeSpan.FromMinutes(10)
);
Supported Redis Connection String Formats:
localhost:6379- No authenticationlocalhost:6379,password=pass- Password onlylocalhost:6379,user=user,password=pass- Username and password (Redis 6+)localhost:6380,ssl=true,password=pass- SSL/TLS connection- Multiple hosts:
server1:6379,server2:6379,password=pass
Cache Features
- Automatic Invalidation: Cache is automatically cleared when flags are created, updated, or deleted
- Configurable Expiration: Set custom cache duration (default: 5 minutes)
- Manual Refresh: Call
RefreshCacheAsync()to manually reload cache - Thread-Safe: All cache operations are thread-safe
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License.
Sample Project
Check out the sample project in samples/Flaggy.Sample.WebApi for a complete working example with PostgreSQL.
Quick Start
# Start PostgreSQL with Docker
cd samples/Flaggy.Sample.WebApi
docker-compose up -d postgres
# Run the application
dotnet run
The application will:
- Automatically create database tables
- Seed 6 demo feature flags
- Create default admin user (admin/admin)
Then navigate to https://localhost:5001/flaggy to see the dashboard.
See the Sample Project README for more details.
<p align="center"> Made with ❤️ for the .NET community </p>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- BCrypt.Net-Next (>= 4.0.3)
- Microsoft.Extensions.Caching.Memory (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- StackExchange.Redis (>= 2.8.16)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Flaggy:
| Package | Downloads |
|---|---|
|
Flaggy.Provider.PostgreSQL
PostgreSQL provider for Flaggy feature flag library |
|
|
Flaggy.Provider.MsSql
MS SQL Server provider for Flaggy feature flag library |
|
|
Flaggy.Provider.MySQL
MySQL provider for Flaggy feature flag library |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 270 | 12/19/2025 |