Maxlona.FeatureFlags
1.4.1
dotnet add package Maxlona.FeatureFlags --version 1.4.1
NuGet\Install-Package Maxlona.FeatureFlags -Version 1.4.1
<PackageReference Include="Maxlona.FeatureFlags" Version="1.4.1" />
<PackageVersion Include="Maxlona.FeatureFlags" Version="1.4.1" />
<PackageReference Include="Maxlona.FeatureFlags" />
paket add Maxlona.FeatureFlags --version 1.4.1
#r "nuget: Maxlona.FeatureFlags, 1.4.1"
#:package Maxlona.FeatureFlags@1.4.1
#addin nuget:?package=Maxlona.FeatureFlags&version=1.4.1
#tool nuget:?package=Maxlona.FeatureFlags&version=1.4.1
Maxlona.FeatureFlags for .NET
The official .NET SDK for Maxlona. It covers the complete public feature-flag API:
- Evaluate boolean, string, number, and JSON variants with targeting context.
- Request decision explanations and read additional evaluation metadata.
- List, get, create, edit, archive, and delete feature flags.
- Configure environment/client state, percentage rollouts, targeting rules, dependencies, variants, and enrollment cutoffs.
- Use application services, structured exceptions, cancellation tokens, and .NET dependency injection.
- Evaluate every check live against the Maxlona service, so a decision always reflects the current flag configuration with nothing to invalidate or refresh.
- Survive an unreliable network: Polly retries with exponential backoff and jitter, a per-attempt timeout, and plain-language firewall/proxy/DNS diagnostics.
- Keep a local Serilog log of every retry and failure, on by default.
The package targets .NET 8 and is published on NuGet.org as Maxlona.FeatureFlags.
Full integration documentation is available in the Maxlona SDK Wiki.
Package
Current SDK release documented here: Maxlona.FeatureFlags 1.4.1
1.4.1 adds per-flag environment management to the live-evaluation package. Every flag check is evaluated live against the Maxlona service. The opt-in encrypted local cache and its realtime SignalR synchronization, added here in 1.3.0, now ship separately as Maxlona.FeatureFlags.Offline; install that package instead if your application evaluates flags while disconnected. Everything else — evaluation, the Management API, the retry pipeline, connection diagnostics, and logging — is unchanged.
Licensed under the Maxlona Proprietary SDK License, included as LICENSE.txt
in the package. Copyright (c) 2026 Maxlona. All rights reserved. This SDK is not
open source. The license permits integration with Maxlona and distribution of
unmodified SDK binaries as part of integrating applications; see the included
license for the full terms. Third-party dependencies retain their own licenses.
| Package information | Value |
|---|---|
| Package ID | Maxlona.FeatureFlags |
| Version | 1.4.1 |
| NuGet.org | https://www.nuget.org/packages/Maxlona.FeatureFlags/1.4.1 |
| Published by | Maxlona |
| Target framework | net8.0 — runs on .NET 8, 9, and 10 |
| Documentation | https://maxlona.com/wiki/sdk |
Dependencies, all pulled in automatically:
| Package | Minimum version |
|---|---|
Microsoft.Extensions.Http |
8.0.1 |
Polly.Core |
8.5.2 |
Serilog |
4.2.0 |
Serilog.Sinks.File |
6.0.0 |
Install
dotnet add package Maxlona.FeatureFlags
A newly published version takes a few minutes to be indexed by NuGet.org. If a version that was just released is not
found yet, wait and retry, or run dotnet restore --no-cache to bypass a stale local index. Versions already
published are unaffected.
Or pin the version:
dotnet add package Maxlona.FeatureFlags --version 1.4.1
<PackageReference Include="Maxlona.FeatureFlags" Version="1.4.1" />
No custom feed is required: NuGet.org is a default source in a standard NuGet configuration. If your build uses a
nuget.config that clears the default sources, restore nuget.org there or point at the internal mirror that
proxies it.
Minimal setup
Register the service with a key and environment, then inject IFeatureFlags into your application:
using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
builder.Services.AddMaxlonaFeatureFlags(
builder.Configuration["Maxlona:ManagementKey"]!, "production");
public sealed class Checkout(IFeatureFlags flags)
{
public Task<bool> UseNewCheckout(string userId) =>
flags.IsEnabledAsync("checkout-redesign",
new EvaluationContext { UserId = userId }, defaultValue: false);
}
For administration, register AddMaxlonaFeatureFlagManagement(managementKey) and inject
IFeatureFlagManagement. The SDK handles endpoint selection, authentication, transport, retries, and logging.
An environment is still required for evaluation because it selects which flag configuration to use.
Evaluate flags
Use a Management Key with Evaluate permission. Scope it to the application, and optionally to one environment. Keep trusted-server keys out of browser-delivered code.
using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
builder.Services.AddMaxlonaFeatureFlags(options =>
{
options.ManagementKey = builder.Configuration["Maxlona:ManagementKey"]!;
options.Stage = "production";
options.ClientId = "checkout-api"; // optional
});
// Resolve IFeatureFlags through DI.
var result = await flags.EvaluateAsync(
"checkout-redesign",
new EvaluationContext
{
UserId = user.Id,
Attributes = new Dictionary<string, object?>
{
["plan"] = user.Plan,
["country"] = user.Country
}
},
explain: true,
cancellationToken);
bool enabled = await flags.IsEnabledAsync(
"checkout-redesign",
new EvaluationContext { UserId = user.Id },
cancellationToken);
CheckoutSettings? settings = await flags.GetValueAsync<CheckoutSettings>(
"checkout-settings",
new EvaluationContext { UserId = user.Id },
cancellationToken);
For console applications without DI, the SDK owns the transport:
using var flags = new FeatureFlags(managementKey, "production");
bool enabled = await flags.IsEnabledAsync("checkout-redesign",
new EvaluationContext { UserId = "user-123" }, defaultValue: false);
using var management = new FeatureFlagManagement(managementKey);
var definitions = await management.ListAsync();
Reuse these services for the application lifetime and dispose them at shutdown. DI manages service lifetimes automatically.
The existing FeatureFlagClient, FeatureFlagManagementClient, and their interfaces remain available for compatibility.
BaseUri is an advanced override for private deployments or tests; ordinary applications never configure an endpoint.
Manage flags
Use the same Management Key format. CanRead keys can list and get; create, update, delete, and configuration operations require CanWrite.
builder.Services.AddMaxlonaFeatureFlagManagement(options =>
{
options.ManagementKey = builder.Configuration["Maxlona:ManagementKey"]!;
});
var created = await management.CreateAsync(new CreateFeatureFlagRequest
{
Name = "checkout-redesign",
Project = "Main App",
Type = "release",
Description = "New checkout flow",
Tags = ["checkout", "q3"],
Variants =
[
FlagVariant.Create("on", true, "Feature enabled"),
FlagVariant.Create("off", false, "Feature disabled")
],
DependsOn = []
}, cancellationToken);
// Add an organization-level opt-in environment before creating a config there.
var environments = await management.GetEnvironmentsAsync(created.Name, cancellationToken);
await management.UpdateEnvironmentsAsync(created.Name, new UpdateFlagEnvironmentsRequest
{
Environments = environments.Environments.Concat(["production"]).Distinct().ToArray()
}, cancellationToken);
await management.UpsertConfigurationAsync(created.Name, new UpsertFlagConfigurationRequest
{
Stage = "production",
Enabled = true,
DefaultVariantKey = "off",
RolloutPercentage = 25,
Rules =
[
new TargetingRule
{
Id = "pro-users",
Priority = 10,
Conditions = [TargetingCondition.Create("plan", "==", "pro")],
Allocations = [new VariantAllocation { VariantKey = "on", Percentage = 100 }]
}
]
}, cancellationToken);
Resilience
Every call goes through a Polly pipeline: 3 retries by default with exponential backoff and jitter, plus a per-attempt timeout (10s by default). All of it applies whether the client came from DI or was constructed by hand.
builder.Services.AddMaxlonaFeatureFlags(options =>
{
options.ManagementKey = builder.Configuration["Maxlona:ManagementKey"]!;
options.Stage = "production";
options.Timeout = TimeSpan.FromSeconds(10);
options.Retry.MaxRetryAttempts = 3; // 0 disables retries
options.Retry.BaseDelay = TimeSpan.FromMilliseconds(200);
options.Retry.MaxDelay = TimeSpan.FromSeconds(5);
options.Retry.HonorRetryAfter = true; // a 429 Retry-After header wins, capped by MaxDelay
});
What gets retried depends on whether repeating the request is safe:
| Failure | Reads, updates, deletes, upserts, evaluations | CreateAsync |
|---|---|---|
| Connection refused / DNS / host unreachable | retried | retried (nothing was delivered) |
| 408, 429, 503 | retried | retried (the server declined it) |
| 500, 502, 504 | retried | not retried (the write may have landed) |
| Connection reset mid-flight, timeout | retried | not retried |
| 400, 401, 403, 404, 409 | never retried | never retried |
Creating the same flag twice is not the same as creating it once, so a create is repeated only when the SDK can prove the request never reached the server.
Degrading gracefully
A feature flag is a configuration lookup, and an outage in a configuration lookup should not take down the feature it configures. Pass a fallback and an unreachable Maxlona stops being an exception:
// Returns false if Maxlona is unreachable or refuses the call. The failure is logged.
bool enabled = await flags.IsEnabledAsync("checkout-redesign", context, defaultValue: false, ct);
CheckoutSettings? settings = await flags.GetValueAsync("checkout-settings", context, Defaults.Checkout, ct);
// Or inspect the failure yourself.
var attempt = await flags.TryEvaluateAsync("checkout-redesign", context, explain: true, ct);
if (!attempt.Succeeded)
logger.LogWarning(attempt.Error, "Falling back to the default variant.");
The overloads without a fallback still throw, for callers that want to know.
Errors and diagnostics
| Exception | Meaning |
|---|---|
FeatureFlagApiException |
The API answered and refused. Carries StatusCode, ErrorCode, TraceId, ResponseBody, RetryAfter. |
FeatureFlagConnectionException |
The API could not be reached. Carries Problem, Guidance, Endpoint, Attempts. |
JsonException |
The response body was not the shape the SDK expected. |
Corporate networks break SDKs in ways that all look identical from .NET: every one of them arrives as the same opaque
HttpRequestException. The SDK classifies them instead and says what to check.
Problem |
Detected from | What the message tells you |
|---|---|---|
DnsFailure |
SocketError.HostNotFound |
The host did not resolve; run nslookup and check for a DNS filter. |
BlockedByFirewall |
connection refused, reset, unreachable, or silently dropped | Allow egress to the host and port in the host firewall, network ACL, and any NSG or security group; set HTTPS_PROXY if a proxy is in use. |
TlsInterception |
AuthenticationException during the handshake |
A TLS-inspecting proxy is re-signing traffic; trust its root certificate or bypass the host. |
ProxyAuthenticationRequired |
HTTP 407 | A proxy wants credentials. Maxlona itself never returns 407. |
InterceptedResponse |
a non-JSON body on a 2xx | A captive portal or web filter answered instead of Maxlona. |
Timeout |
the attempt timeout elapsed | Nothing answered, the signature of a firewall that drops rather than refuses. |
Check the whole path at startup, without throwing:
var report = await management.CheckConnectivityAsync(ct);
if (!report.IsHealthy)
logger.LogError("Maxlona unreachable: {Summary} {Guidance}", report.Summary, report.Guidance);
IsHealthy false with a null Problem means the network is fine and the API refused: a revoked key, a scope
mismatch, or an inactive subscription. Problem set means the traffic never got there.
Logging
The SDK writes its own Serilog file log, on by default, recording every retry, every classified failure, and its
guidance. It never touches Serilog.Log.Logger, so it cannot disturb the host application logging setup.
Logs default to AppContext.BaseDirectory/maxlona-FFlags, beside the running application.
Set options.Logging.Directory to an absolute directory or a relative path such as logs/flags.
Relative paths resolve from AppContext.BaseDirectory, regardless of the shell's working directory.
If the chosen directory is unwritable, file logging is skipped without failing flag operations.
Set options.Logging.Enabled = false to disable file logging, or supply your own Serilog logger.
Files roll daily as maxlona-featureflags-<date>.log, keeping 7 of them, capped at 16 MB each.
options.Logging.Enabled = true; // default
options.Logging.Directory = @"D:\logs\maxlona"; // override the location
options.Logging.MinimumLevel = LogEventLevel.Warning;
options.Logging.RetainedFileCountLimit = 7;
options.Logging.FileSizeLimitBytes = 16 * 1024 * 1024;
options.Logging.Logger = Log.Logger; // or fold into your own Serilog logger
Logging is best-effort by design: if the folder cannot be created or the file cannot be opened, the SDK degrades to writing nothing rather than failing the call. Losing a log line must never cost a flag evaluation.
Credential permissions
Every SDK request uses x-management-key. Grant only the required permissions:
CanEvaluate: resolve flag values throughPOST /flags/{name}; application scope is required and environment scope is optional.CanRead: list and retrieve flag definitions through the Management API.CanWrite: create, edit, configure, archive, and delete flags; read access is included.
The key is mandatory. The SDK validates it when the client is constructed, including at DI registration, so a
missing, blank, or unsendable key fails at startup rather than as a puzzling 401 on the first user request. A key
carrying whitespace or a non-ASCII look-alike character (the usual result of copying one out of a rich-text document)
is rejected explicitly, because HttpClient would otherwise drop the header and send the request with no credential
at all.
Keep the key in a secret store or environment variable. Do not commit it.
Coverage
The SDK supports the following flag evaluation and management operations:
| Endpoint | SDK method | Permission |
|---|---|---|
POST /flags/{key} |
EvaluateAsync, TryEvaluateAsync, IsEnabledAsync, GetValueAsync |
CanEvaluate |
GET /api/flags |
ListAsync |
CanRead |
GET /api/flags/{name} |
GetAsync, TryGetAsync, ExistsAsync |
CanRead |
GET /api/flags/{name}/environments |
GetEnvironmentsAsync |
CanRead |
POST /api/flags |
CreateAsync |
CanWrite |
PUT /api/flags/{name} |
UpdateAsync, ArchiveAsync, RestoreAsync |
CanWrite |
PUT /api/flags/{name}/environments |
UpdateEnvironmentsAsync |
CanWrite |
DELETE /api/flags/{name} |
DeleteAsync |
CanWrite |
POST /api/flags/{name}/configs |
UpsertConfigurationAsync |
CanWrite |
Use the Maxlona console to manage organisations, billing, users, groups, segments, webhooks, experiments, and approvals. These operations are not available through this SDK.
For organization environments configured as opt-in, call UpdateEnvironmentsAsync
before creating the flag's configuration in that environment. Replacing the
environment set can remove disabled configurations and can return a conflict when
an enabled configuration, automation, approval policy, or dependency blocks the change.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Http (>= 8.0.1)
- Polly.Core (>= 8.5.2)
- Serilog (>= 4.2.0)
- Serilog.Sinks.File (>= 6.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.4.1 adds per-flag environment fields and Management API operations for reading and replacing a flag's resolved environment set, including dependency conflict resolution.