bitwarden/server
Feature flags
Active contributors: every product team.
Purpose
IFeatureService is the runtime-toggle abstraction every team uses to gate new code. In the cloud it is backed by LaunchDarkly; in self-host it returns the static defaults defined in code (or in the operator's appsettings.SelfHosted.json).
Directory layout
src/Core/Services/
├── IFeatureService.cs # interface
└── Implementations/
├── LaunchDarklyFeatureService.cs # cloud
├── ConfigurationFeatureService.cs # self-host: reads from appsettings
└── NoopFeatureService.cs # tests / minimal hosts
src/Core/Constants.cs # FeatureFlagKeys constants live hereAdding a flag
- Add a
public const string My_Flag = "my-flag";to theFeatureFlagKeysstatic class insrc/Core/Constants.cs. The string value is the LaunchDarkly key; the C# constant should be PascalCase. - Inject
IFeatureServiceand callIsEnabled(FeatureFlagKeys.My_Flag, currentContext). - Configure the flag in LaunchDarkly with a default value of
false. Cloud rollout will flip it on. - For self-host, the flag returns the LaunchDarkly default (currently coded as
falsefor unknown keys); admins can override per-flag viaglobalSettings.LaunchDarklyconfiguration if needed.
CI workflow .github/workflows/code-references.yml exports a list of every FeatureFlagKeys.X reference and feeds it back to LaunchDarkly so unused flags can be culled.
Removing a flag
Once a flag is fully rolled out:
- Inline the "true" branch and delete the alternate code path.
- Remove the constant from
FeatureFlagKeys. - Delete the LaunchDarkly definition.
- Search
git log --grep "remove feature flag"for prior examples — the team typically titles these PRschore(<team>): remove unused FF.
How it works
graph LR
Code["if (await _ff.IsEnabledAsync(FeatureFlagKeys.X, ctx))"] --> Service[IFeatureService]
Service --> LD[LaunchDarklyFeatureService\n(cloud)]
Service --> Cfg[ConfigurationFeatureService\n(self-host)]
Service --> Noop[NoopFeatureService\n(tests)]IFeatureService.IsEnabledAsync(key, ctx) evaluates the flag with a LaunchDarkly context built from the current user / org / environment. Targeting can therefore key off org id, user id, plan type, country, etc. The current-context-to-LD-context mapping lives in LaunchDarklyFeatureService.BuildContext.
Conventions
- Flags follow kebab-case in LaunchDarkly (
bulk-auto-confirm-on-login) and PascalCase in C# (BulkAutoConfirmOnLogin). - Keep flags short-lived. The codebase has accumulated many "remove flag" PRs (e.g. PR #7549 "chore(billing): remove unused FF"); aim to retire flags within a release or two.
- Prefer feature flags over branch toggles — they keep PRs small and deployable.
Entry points for modification
- Add a new flag → see steps above.
- Switch to a different provider → implement
IFeatureServiceand register it conditionally inservices.AddDefaultServices. - Add a flag-aware authorization handler → inject
IFeatureServiceand short-circuit the handler if the flag is off.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.