Flags in appsettings.json are a fine start. Then someone has to turn one off at 4:55 on a Friday, and "change a config file" turns out to mean "run the deployment pipeline." This walks through moving those flags into a dashboard, using FeatureFlags.app, without rewriting the code that checks them.
What changes and what doesn't
IFeatureManager, IFeatureManagerSnapshot, [FeatureGate], the <feature> tag helper, and the built-in filters keep working. What changes is where flags come from: a package, one registration call, and a configuration section.
Step 1: Take inventory
List every flag in your FeatureManagement section and the filters it uses. Percentage rollouts, time windows, and user targeting all have dashboard equivalents. Custom filters can be added as JSON and keep working as long as your app still implements the filter class.
Step 2: Create the flags
Create an environment for each place your app runs, then recreate each flag in the matching environment with the same name and filters. Keep names identical to your code, and don't use a colon in one. You can also import flags from the feature_management section of your appsettings.json from the flag list. Finish this before you deploy. Step 4 explains why.
Step 3: Create an API key
Each API key reads the flags of one environment. Keep keys out of source control:
dotnet user-secrets set "FeatureFlags:ApiKey" "your-api-key-here"
Step 4: Swap the registration
dotnet add package Acmi.FeatureFlags.Client
Replace AddFeatureManagement() with AddFeatureFlags(). Replace, don't add: if both are called, the app fails at startup with a FeatureManagementException, because AddFeatureFlags() already registers Microsoft's feature management.
using Acmi.FeatureFlags.Client;
builder.AddFeatureFlags();
{
"FeatureFlags": {
"ApiBaseEndpoint": "https://featureflags.app/api/",
"ApiKey": "[from secrets]"
}
}
Here is what bites people: once the client is registered, the local FeatureManagement section is no longer read. A flag that exists only in appsettings.json evaluates to off. Check each flag with the dashboard's Evaluate page before you deploy, then delete the old section.
Step 5: Know the failure modes
The client fetches flags at startup, then every 15 minutes by default (CacheExpirationInMinutes), so changes reach your app within that interval. If a refresh fails, it keeps the last flags it fetched. If your app starts while the service is unreachable, every flag is off until the first successful fetch. Apps that rely on kill switches should lower the interval.
Going back: export
On the Feature Flags page, choose Export, pick an environment, and download or copy every flag in it as standard Microsoft.FeatureManagement JSON. It's available on every plan, even over your flag limit. Paste it into appsettings.json (it uses a feature_management section), remove AddFeatureFlags(), and put AddFeatureManagement() back. Percentage rollouts export as Microsoft's Targeting filter, so your app needs .WithTargeting():
builder.Services.AddFeatureManagement().WithTargeting();
Custom filters are exported as-is and need their filter classes. Your flag-checking code never noticed.
The best migration is the one where nobody notices anything changed. Except the Friday-afternoon you.