| name | manage-settings-and-features |
| description | ABP settings (config values) and features (tenant/edition on/off or valued capabilities). USE FOR: SettingDefinitionProvider / ISettingProvider, FeatureDefinitionProvider / IFeatureChecker / [RequiresFeature], custom value providers, ISettingManager and IFeatureManager. DO NOT USE FOR: typed distributed caching or locking (distributed-caching-and-locking); user/role authorization (permissions-and-authorization); multi-tenant resolution (configure-multi-tenancy). |
| license | MIT |
Managing Settings and Features in ABP
Settings and features look similar (both are name/value pairs resolved through a provider fallback chain) but answer different questions:
- Settings = configuration values for the application/tenant/user (SMTP host, default timezone, page size). Resolved per user > tenant > global > configuration > default. Used everywhere, not tied to multi-tenancy.
- Features = capabilities you grant to a tenant (or edition), usually boolean on/off or a limit value (max product count). Designed for SaaS/multi-tenant: "does this tenant's plan include PDF reporting?".
Rule of thumb: if a customer's plan/edition controls it, it's a feature; if it's a tunable configuration value, it's a setting.
When to Use
- Declare tunable configuration values (SMTP host, page size) and read them with provider fallback.
- Grant per-tenant or per-edition capabilities (on/off or valued) for a SaaS app.
- Gate a service method or class behind a feature declaratively or imperatively.
- Change setting or feature values from code, or add a custom value provider.
When Not to Use
- Typed distributed caching or cross-instance locking — use the distributed-caching-and-locking skill.
- Authorizing users/roles with permissions — use the permissions-and-authorization skill. Features gate a tenant's plan, permissions gate a user's rights.
- Configuring or resolving multi-tenancy itself — use the configure-multi-tenancy skill.
Settings
Defining settings
Derive from SettingDefinitionProvider (auto-discovered, transient). For a DDD module this usually lives in the Domain layer.
public class EmailSettingProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition("Smtp.Host", "127.0.0.1"),
new SettingDefinition("Smtp.Port", "25"),
new SettingDefinition("Smtp.UserName"),
new SettingDefinition("Smtp.Password", isEncrypted: true),
new SettingDefinition("Smtp.EnableSsl", "false")
);
}
}
SettingDefinition constructor parameters (only name is mandatory):
name, defaultValue = null, displayName = null, description = null, isVisibleToClients = false, isInherited = true, isEncrypted = false.
IsVisibleToClients defaults to false — set it true to expose the value to browser/JS code.
IsEncrypted encrypts on save / decrypts on read via ISettingEncryptionService.
- Define setting names as
const string instead of magic strings.
To tweak a setting defined by a depended module, query and mutate it in your own provider:
var smtpHost = context.GetOrNull("Abp.Mailing.Smtp.Host");
if (smtpHost != null)
{
smtpHost.DefaultValue = "mail.mydomain.com";
}
Reading setting values
Inject ISettingProvider. Its interface methods are GetOrNullAsync(name) → Task<string?>, GetAllAsync(string[] names) and GetAllAsync() → Task<List<SettingValue>>; the typed/bool helpers like IsTrueAsync and GetAsync<T> are extension methods.
string? userName = await _settingProvider.GetOrNullAsync("Smtp.UserName");
bool enableSsl = await _settingProvider.IsTrueAsync("Smtp.EnableSsl");
int port = await _settingProvider.GetAsync<int>("Smtp.Port");
IApplicationService already property-injects ISettingProvider (use the SettingProvider property).
Value providers and store
ISettingProvider resolves a value by walking 5 built-in providers; the first that returns non-null wins. Fallback goes bottom → top:
UserSettingValueProvider (U) > TenantSettingValueProvider (T) > GlobalSettingValueProvider (G) > ConfigurationSettingValueProvider (C) > DefaultValueSettingValueProvider (D).
- Configuration provider reads the
"Settings" section of appsettings.json (or env / user secrets).
- Global/Tenant/User providers read through
ISettingStore. The core ships NullSettingStore (always null); the Setting Management module implements a database-backed ISettingStore — that's what actually persists changed values.
- Custom provider: derive from
SettingValueProvider (unique Name), then register via Configure<AbpSettingOptions>(o => o.ValueProviders.Add<CustomSettingValueProvider>()).
Features
Defining features
Derive from FeatureDefinitionProvider (auto-discovered). Usually placed in the Application.Contracts project. You add a group first, then features under it.
using Volo.Abp.Features;
using Volo.Abp.Validation.StringValues;
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false");
myGroup.AddFeature(
"MyApp.MaxProductCount",
defaultValue: "10",
valueType: new FreeTextStringValueType(new NumericValueValidator(0, 1000000))
);
}
}
Optional feature properties: DisplayName, Description, ValueType (ToggleStringValueType for on/off, FreeTextStringValueType for free text, SelectionStringValueType for a dropdown), IsVisibleToClients (default true), Properties.
Child features (selectable only when the parent is enabled) via CreateChild(...) on the returned FeatureDefinition. Modify a depended module's feature with context.GetGroupOrNull("SomeModule").
Checking features
Two ways: the declarative [RequiresFeature] attribute or the IFeatureChecker service.
public class ReportingAppService : ApplicationService, IReportingAppService
{
[RequiresFeature("MyApp.PdfReporting")]
public async Task<PdfReportResultDto> GetPdfReportAsync() { return await GenerateReportAsync(); }
}
[RequiresFeature] works on a method or a class. Multiple names check "any enabled"; set RequiresAll = true to require all.
- It relies on interception: for non-interface services methods must be
virtual (class-proxy requirement); an async/Task-returning method is recommended but not required. Controllers/Razor pages are exempt (filters handle them).
IFeatureChecker for imperative checks. Interface methods: IsEnabledAsync(name), IsEnabledAsync(string[] names), GetOrNullAsync(name) → Task<string?>. Typed/check helpers are extensions.
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) { }
int max = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount");
await _featureChecker.CheckEnabledAsync("MyApp.PdfReporting");
Value providers, management, and configuration
Feature values resolve through providers (first non-null wins), in order:
TenantFeatureValueProvider > EditionFeatureValueProvider (edition id from AbpClaimTypes.EditionId) > ConfigurationFeatureValueProvider (reads the "Features" section of appsettings.json) > DefaultValueFeatureValueProvider.
Custom provider: derive from FeatureValueProvider, register via Configure<AbpFeatureOptions>(o => o.ValueProviders.Add<...>()).
Persistence is IFeatureStore — the Feature Management module implements it (DB-backed) and ships in the startup template. To change feature values from code inject IFeatureManager:
await _featureManager.SetForTenantAsync(tenantId, "MyApp.PdfReporting", true.ToString());
Admins normally set feature values via the feature-management modal on the Tenant Management page.
To change setting values from code, inject ISettingManager (namespace Volo.Abp.SettingManagement, from the Setting Management module package Volo.Abp.SettingManagement.Domain). It persists to the correct provider scope:
await _settingManager.SetGlobalAsync("MyApp.SmtpHost", "smtp.acme.com");
await _settingManager.SetForTenantAsync(tenantId, "MyApp.SmtpHost", "smtp.tenant.com");
await _settingManager.SetForUserAsync(userId, "MyApp.Theme", "dark");
ISettingProvider (read) is in the framework, but writing needs the Setting Management module — a host that only references Volo.Abp.Settings can read but not persist changes.
Validation
- Build the app;
SettingDefinitionProvider and FeatureDefinitionProvider are auto-discovered at startup.
- Read a setting back through
ISettingProvider and confirm the fallback resolves the expected layer (e.g. an appsettings.json "Settings" value overrides the default).
- Confirm a
[RequiresFeature]-guarded method throws AbpAuthorizationException when the feature is disabled and runs when enabled.
- Confirm persisted values require the Setting Management / Feature Management module (the core
NullSettingStore always returns null, so without it only configuration/default values resolve).
Common Pitfalls
[RequiresFeature] silently not enforced — it relies on interception, so for non-interface services the method must be virtual (class-proxy requirement). An async/Task-returning method is recommended but not required. Controllers/Razor pages are exempt (filters handle them instead).
- Expecting changed setting/feature values to persist without the management module — the core ships
NullSettingStore (always null); DB persistence comes from the Setting Management / Feature Management modules.
- Confusing features with permissions — features gate a tenant/edition's plan, permissions gate a user's rights. Don't use a feature where a permission belongs.
IsVisibleToClients defaults differ — settings default to false (hidden from browser/JS), features default to true. Set it explicitly when the default is wrong for your case.
- Using magic strings for names — define setting/feature names as
const string to avoid typos that resolve to a missing (null) value.