Feature flags
How the kit switches features on per organization: flags with a default, a rollout share and per-organization overrides, read by code and by the web, managed by super-admins.
A feature flag turns a feature on for the platform, for a share of the organizations, or for chosen ones. Code asks IFeatureFlags, the web reads every flag of the organization at once, and super-admins manage the flags.
How a flag is decided
For an organization, a flag is:
- the organization's override, when there is one;
- otherwise on when the flag is enabled and the organization's bucket is below the flag's rollout share.
The bucket is a number from 0 to 99 drawn from the flag's key and the organization's id (SHA-256), the same on every replica and every run. A rollout of 20% therefore reaches the same fifth of the organizations until it changes, and raising it only adds organizations. An unknown flag is off.
Using a flag in code
if (await featureFlags.IsEnabledAsync("reports.export", organizationId, cancellationToken))
{
// the feature
}
An organization endpoint that belongs to a flagged feature declares it, and answers 404 while the flag is off, so the feature's existence is not revealed:
group.MapGet("/reports/export", ExportReports.HandleAsync).RequireFeature("reports.export");
The web
GET /v1/organizations/{organizationId}/features returns { "features": { "reports.export": true, ... } } to any member: every flag with its value for the organization.
Managing flags
Super-admins, with a second factor, under /v1/admin/feature-flags:
| Endpoint | Does |
|---|---|
GET /v1/admin/feature-flags | The flags with their default, rollout and number of overrides. |
POST /v1/admin/feature-flags with { "key": "reports.export", "description": "...", "enabled": true, "rolloutPercentage": 20 } | Creates a flag. The key uses lowercase letters and digits separated by single dots or dashes, up to 64 characters; the rollout defaults to 100. |
GET /v1/admin/feature-flags/{key} | A flag with the organizations that override it. |
PATCH /v1/admin/feature-flags/{key} with any of description, enabled, rolloutPercentage | Changes a flag. |
PUT /v1/admin/feature-flags/{key}/overrides/{tenantId} with { "enabled": true } | Turns the flag on or off for one organization. |
DELETE /v1/admin/feature-flags/{key}/overrides/{tenantId} | Returns the organization to the flag's default. |
Cache and audit
The flags are cached under one tag and each organization's overrides under its own key, for ten minutes. A change drops them at once on every replica sharing the Redis cache, so the next read sees it. A flag is platform-wide: its creation and changes go to the operators' log with the super-admin. An override is a change in one organization and is recorded in its audit log (feature_override.created, .updated, .deleted).