Feature Flags
Wayd uses Microsoft.FeatureManagement to control feature visibility. Feature flags are defined in code, stored in the database, and managed through the Settings UI.
How It Works
- Flags are defined in code (
FeatureFlags.cs) - On startup, the seeder creates any new flags in the database. New flags are seeded disabled by default, unless the definition sets
DefaultEnabled: true. The seeder only sets the initial state on first creation — it never overwrites an existing flag's state, so admin toggles are preserved across deploys. - Admins enable/disable flags through the Settings UI
- A database-backed provider serves flag state with caching
Current Flags
| Name | Display name | Controls |
|---|---|---|
planning-poker | Planning Poker | The Planning Poker feature |
story-maps | Story Maps | The Story Maps feature |
product-management | Product Management | The Product Management module |
delivery-forecasting | Delivery Forecasting | Monte Carlo delivery forecasts |
mcp-server | MCP Server | The hosted MCP endpoint (/mcp) |
team-capacity-management | Team Capacity Management | Employee allocation to teams and team capacity planning |
All of them are seeded disabled.
Adding a New Feature Flag
1. Define the Flag
In Wayd.Common.Domain/FeatureManagement/FeatureFlags.cs:
public static class FeatureFlags
{
public static readonly FeatureFlagDefinition MyNewFeature =
new(Names.MyNewFeature, "My New Feature", "Description of what this feature does.");
// Seed a flag as ENABLED by default (e.g. rolling out a replacement UI):
public static readonly FeatureFlagDefinition MyDefaultOnFeature =
new(Names.MyDefaultOnFeature, "My Default-On Feature", "Enabled on first seed.", DefaultEnabled: true);
public static class Names
{
public const string MyNewFeature = "my-new-feature";
public const string MyDefaultOnFeature = "my-default-on-feature";
}
}
2. Gate the Backend
On a controller or action:
[FeatureGate(FeatureFlags.Names.MyNewFeature)]
[HttpGet]
public async Task<ActionResult<MyDto>> Get() { ... }
Use the controller attribute for an endpoint that exists only with the flag on. For behaviour a flag changes inside an endpoint, inject IFeatureFlags (Wayd.Common.Application.FeatureManagement) into the handler — the application layers do not reference Microsoft.FeatureManagement:
if (!await _featureFlags.IsEnabled(FeatureFlags.Names.MyNewFeature, cancellationToken))
return Result.Failure("Feature is not enabled.");
3. Gate the Frontend
Page-level gate:
export default requireFeatureFlag(MyPage, 'my-new-feature')
While the flag is off, the page answers 404.
Conditional rendering (the hook returns { isEnabled, isLoading } and takes the
kebab-case flag name):
const { isEnabled, isLoading } = useFeatureFlag('my-new-feature')
if (isLoading) return <Spin />
if (!isEnabled) return <LegacyView />
return <NewView />
4. Gate Menus
Pass flag state into menu builder functions to conditionally show navigation items.
Key Files
| File | Purpose |
|---|---|
Wayd.Common.Domain/FeatureManagement/FeatureFlags.cs | Flag definitions |
Wayd.Infrastructure/Persistence/Initialization/FeatureFlagSeeder.cs | Auto-seeds flags |
Wayd.Infrastructure/FeatureManagement/DatabaseFeatureDefinitionProvider.cs | Database provider |
Wayd.Common.Application/FeatureManagement/IFeatureFlags.cs | Flag checks in handlers |
wayd.web.reactclient/src/hooks/use-feature-flag.ts | Frontend hook |
wayd.web.reactclient/src/components/hoc/require-feature-flag.tsx | Page-level gate |