Skip to main content

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​

  1. Flags are defined in code (FeatureFlags.cs)
  2. 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.
  3. Admins enable/disable flags through the Settings UI
  4. A database-backed provider serves flag state with caching

Current Flags​

NameDisplay nameControls
planning-pokerPlanning PokerThe Planning Poker feature
story-mapsStory MapsThe Story Maps feature
product-managementProduct ManagementThe Product Management module
delivery-forecastingDelivery ForecastingMonte Carlo delivery forecasts
mcp-serverMCP ServerThe hosted MCP endpoint (/mcp)
team-capacity-managementTeam Capacity ManagementEmployee 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​

FilePurpose
Wayd.Common.Domain/FeatureManagement/FeatureFlags.csFlag definitions
Wayd.Infrastructure/Persistence/Initialization/FeatureFlagSeeder.csAuto-seeds flags
Wayd.Infrastructure/FeatureManagement/DatabaseFeatureDefinitionProvider.csDatabase provider
Wayd.Common.Application/FeatureManagement/IFeatureFlags.csFlag checks in handlers
wayd.web.reactclient/src/hooks/use-feature-flag.tsFrontend hook
wayd.web.reactclient/src/components/hoc/require-feature-flag.tsxPage-level gate