Moda uses Microsoft.FeatureManagement to control feature visibility across the application. Feature flags are stored in the database and managed through the Settings UI.
Feature flags allow features to be toggled on or off at runtime without a code deployment. They are used to:
All feature flags are code-first — a developer defines the flag in code and adds the corresponding checks. The flag is automatically created in the database on application startup via the seeder.
| Type | Created By | Archivable | Purpose |
|---|---|---|---|
| System | Seeder (from code) | No | Gate application features defined in code |
| User | Admin (from UI) | Yes | Reserved for future use |
Currently, all feature flags should be defined as system flags in code.
Add the flag definition to Moda.Common.Domain/FeatureManagement/FeatureFlags.cs:
public static class FeatureFlags
{
public static readonly FeatureFlagDefinition PlanningPoker = new(
Names.PlanningPoker,
"Planning Poker",
"Controls visibility of the Planning Poker feature.");
// Add your new flag here
public static readonly FeatureFlagDefinition MyNewFeature = new(
Names.MyNewFeature,
"My New Feature",
"Description of what this flag controls.");
public static class Names
{
public const string PlanningPoker = "planning-poker";
public const string MyNewFeature = "my-new-feature"; // kebab-case
}
}
Naming conventions:
planning-poker, bulk-work-item-import)Names nested class provides compile-time constants for use in attributes like [FeatureGate]The FeatureFlagSeeder automatically discovers all FeatureFlagDefinition fields via reflection and creates any missing flags in the database on startup. New flags are created as disabled by default.
Use [FeatureGate] to gate an entire controller. When the flag is disabled, all endpoints return 404:
[FeatureGate(FeatureFlags.Names.MyNewFeature)]
public class MyController(ISender sender) : ControllerBase
{
// All endpoints gated by the flag
}
Apply [FeatureGate] to individual actions if only some endpoints need gating:
[FeatureGate(FeatureFlags.Names.MyNewFeature)]
[HttpGet]
public async Task<ActionResult> GetList() { ... }
Use IFeatureManager for runtime checks in business logic:
public class MyCommandHandler(ISender sender, IFeatureManager featureManager)
{
public async Task<Result> Handle(MyCommand command, CancellationToken ct)
{
if (!await featureManager.IsEnabledAsync(FeatureFlags.Names.MyNewFeature))
return Result.Failure("This feature is not currently available.");
// ... handler logic
}
}
Use the requireFeatureFlag HOC to prevent a page from rendering when the flag is off. This triggers a 404 and prevents any API calls from being made:
import { authorizePage, requireFeatureFlag } from '@/src/components/hoc'
const MyPage = () => { ... }
// Apply both permission and feature flag checks
// Feature flag wraps the outside — checked first
export default requireFeatureFlag(
authorizePage(MyPage, 'Permission', 'Permissions.MyFeature.View'),
'my-new-feature',
)
Use the useFeatureFlag hook for conditional rendering within components:
import { useFeatureFlag } from "@/src/hooks";
const MyComponent = () => {
const myFeatureEnabled = useFeatureFlag("my-new-feature");
if (!myFeatureEnabled) return null;
return <MyFeatureUI />;
};
Gate menu items by passing feature flag state into menu builder functions:
const buildMenuItems = (featureFlags: { myNewFeature: boolean }) => [
...(featureFlags.myNewFeature
? [menuItem("My Feature", "my-feature", "/my-feature")]
: []),
];
const useAppMenuItems = () => {
const myNewFeature = useFeatureFlag("my-new-feature");
return buildMenuItems({ myNewFeature });
};
Moda.Common.Domain/FeatureManagement/FeatureFlag.csModa.Common.Domain/FeatureManagement/FeatureFlags.csModa.Infrastructure/Persistence/Initialization/FeatureFlagSeeder.csModa.Infrastructure/FeatureManagement/DatabaseFeatureDefinitionProvider.cs — implements IFeatureDefinitionProvider with 30-second cachingModa.Web.Api/Controllers/FeatureManagement/ — admin CRUD and client endpoint for enabled flagshooks/use-feature-flag.ts — polls enabled flags every 60 secondscomponents/hoc/require-feature-flag.tsx — triggers 404 when flag is offapp/settings/feature-management/feature-flags/ — grid with toggle/archive actions and detail drawerstore/features/feature-flags-api.ts (client) and store/features/admin/feature-flags-api.ts (admin)Feature flag evaluations are traced via OpenTelemetry using the Microsoft.FeatureManagement ActivitySource. Each feature definition has telemetry enabled, so flag evaluations appear in traces when running with Aspire or an OTLP endpoint.
| Name | Display Name | Description |
|---|---|---|
planning-poker |
Planning Poker | Controls visibility of Planning Poker and Estimation Scales features |