Skip to content

Feature Flags

pkg/features/flags provides config-driven feature flags via DI. Flags are defined entirely in your config file, read lock-free at runtime, and hot-reloaded when the config changes — no external flag service required.

Add the module to your runtime. It provides *flags.Flags to the injector.

lakta.NewRuntime(
config.NewModule(
config.WithConfigDirs(".", "./config"),
config.WithArgs(os.Args[1:]),
),
tint.NewModule(),
slog.NewModule(),
otel.NewModule(),
flags.NewModule(),
)

Define flags in config. A scalar is a plain value; the {value, rollout} object form gates a boolean by a percentage. Prefer snake_case names — hyphenated names cannot be overridden via environment variables.

modules:
features:
flags:
default:
flags:
new_dashboard: true
max_batch_size: 500
checkout_v2:
value: true
rollout: 25

Invoke *flags.Flags from DI and read typed values. Each accessor takes a default returned when the flag is missing or mistyped.

f, err := lakta.Invoke[*flags.Flags](ctx)
if err != nil {
return err
}
if f.Bool(ctx, "new_dashboard", false) {
// ...
}
size := f.Int(ctx, "max_batch_size", 100)

Typed accessors: Bool, String, Int, Float, and Duration. Coercion is lenient with strings so environment-variable overrides (which always arrive as strings) still resolve to the right type.

BoolFor gates a boolean flag by its rollout percentage. A stable FNV hash of the flag name and the key you pass decides whether the key falls in the enabled bucket, so the same key is consistently in or out. Hashing the flag name too decorrelates rollouts — two 50% flags enable different key sets.

if f.BoolFor(ctx, "checkout_v2", userID, false) {
// enabled for ~25% of user IDs, stably
}

Without a rollout, BoolFor behaves like Bool. Plain Bool ignores rollout percentages entirely.

The module implements config hot-reload: on a config change it re-parses the flag definitions and swaps the snapshot wholesale, so reads never observe a half-applied reload. If the new definitions fail to parse, the previous snapshot is kept and the error is logged.

Config path: modules.features.flags.<name>

flagsmap[string]any

flags holds the raw flag definitions: scalars for plain values, or

envLAKTA_MODULES__FEATURES__FLAGS__<NAME>__FLAGS