clapfig
Rich, layered configuration for Rust applications. Define a struct, point at your files, and go.
clapfig discovers, merges, and manages configuration from multiple sources — config files, environment variables, and programmatic overrides — through a pure Rust builder API. The core library has no dependency on any CLI framework: you can use it in GUI apps, servers, or with any argument parser. For clap users, an optional adapter provides drop-in config gen|list|get|set subcommands with zero boilerplate.
Built on confique for struct-driven defaults and commented template generation.
Features
Core (always available, no CLI framework needed):
- Struct as source of truth — define settings as a Rust struct with defaults and
///doc comments - Layered merge — defaults < config files < env vars < overrides, every layer sparse
- Multi-path file search — platform config dir, home, cwd, ancestor walk, or any path, in precedence order
- Search modes — merge all found configs (layered overrides) or use the first match ("find my config")
- Ancestor walk —
SearchPath::Ancestorswalks up from cwd to find project configs, with configurable boundary (.git, filesystem root) - Prefix-based env vars —
MYAPP__DATABASE__URLmaps todatabase.urlautomatically - Strict mode — unknown keys in config files error with file path, key name, and line number (on by default)
- Template generation — emit a commented sample config derived from the struct's doc comments
- Persistence with named scopes —
persist_scope("local", path)/persist_scope("global", path)for global/local config patterns. Scope paths auto-added to search paths.
Clap adapter (clap feature, on by default):
- Config subcommand — drop-in
config gen|get|set|listcommands for clap --scopeflag — target a specific scope for any config subcommand (e.g.config set key val --scope global)- Auto-matching overrides — map clap args to config keys by name in one call
Quick Start
Define your config with confique's Config derive:
use Config;
use ;
Load it in one line:
use Clapfig;
That app_name("myapp") call sets sensible defaults:
- Searches for
myapp.tomlin the platform config directory - Merges env vars prefixed with
MYAPP__ - Fills in
#[config(default)]values for anything not provided
Setup
Defaults from app_name
| Derived setting | Value |
|---|---|
| File name | {app_name}.toml |
| Search paths | Platform config dir (via directories) |
| Env prefix | {APP_NAME} (uppercased) |
Builder methods
use ;
let config: AppConfig = builder
// Required — sets defaults for file_name, search_paths, env_prefix
.app_name
// Optional overrides
.file_name // override config file name
.search_paths // replace default search paths
.add_search_path // append a path without replacing
.env_prefix // override env var prefix
.no_env // disable env var loading entirely
.strict // disable strict mode (allow unknown keys)
.cli_override // override a key from a CLI arg
.load?;
Search Paths, Modes, and Persistence
Config file handling has three orthogonal axes on the builder:
- Discovery (
.search_paths()) — where to look. Paths listed in priority-ascending order (last = highest). - Resolution (
.search_mode()) —Merge(default: deep-merge all found files) orFirstMatch(use the single highest-priority file). - Persistence (
.persist_scope(name, path)) — named targets forconfig setwrites. Scope paths are auto-added to search paths.
use ;
// Layered global + local with named scopes
let config: AppConfig = builder
.app_name
.search_paths
.persist_scope // default for writes
.persist_scope
.load?;
// Find nearest project config (walk up to .git, use first match)
let config: AppConfig = builder
.app_name
.search_paths
.search_mode
.load?;
With scopes configured, the --scope flag targets specific config files:
Available SearchPath variants: Platform, Home(".myapp"), Cwd, Path(PathBuf), Ancestors(Boundary).
Ancestors walks up from cwd, expanding inline into multiple directories (shallowest first, cwd last = highest priority). Boundary::Root walks to the filesystem root; Boundary::Marker(".git") stops at the directory containing the marker (inclusive).
Missing files are silently skipped. See the types module docs for the full conceptual guide and use-case examples.
Strict Mode
Strict mode is on by default. If a config file contains a key that doesn't match any field in your struct, loading fails with a clear error including the file path, key name, and line number:
Unknown key 'typo_key' in /home/user/.config/myapp/myapp.toml (line 5)
Disable it with .strict(false) if you want to allow extra keys.
Normalizing Values
You can normalize config values during deserialization using confique's #[config(deserialize_with = ...)] attribute. The function has the standard serde deserializer signature and runs automatically when a value is loaded from any source — config files, environment variables, or programmatic overrides.
use Config;
use ;
/// Normalize a string to lowercase during deserialization.
With this, color = "BLUE" in a TOML file, MYAPP__COLOR=Blue as an env var, or .cli_override("color", "RED") all resolve to their lowercase form. Note that #[config(default)] values are injected directly by confique and do not pass through the deserializer — if your default needs normalization, write it in normalized form.
Environment Variables
With env prefix MYAPP, variables map via double-underscore nesting:
| Env var | Config key |
|---|---|
MYAPP__HOST |
host |
MYAPP__PORT |
port |
MYAPP__DATABASE__URL |
database.url |
MYAPP__DATABASE__POOL_SIZE |
database.pool_size |
__ (double underscore) separates nesting levels. Single _ within a segment is literal (part of the field name).
Disable env loading entirely with .no_env().
Programmatic Overrides
The cli_override and cli_overrides_from methods on the builder work with any value source — they are not clap-specific despite the name. Use them to inject overrides from CLI args, GUI inputs, HTTP requests, or anything else.
Auto-matching
If your override source derives Serialize, cli_overrides_from auto-matches fields by name against config keys:
use Parser;
use Serialize;
cli_overrides_from(source) serializes the source, skips None values, and keeps only keys that match a config field. Non-matching fields (command, db_url) are silently ignored. Works with any Serialize type — structs, HashMaps, etc.
Manual overrides
For fields where the CLI name differs from the config key, use cli_override:
.cli_override
cli_override(key, value) takes Option<V> where V: Into<toml::Value> — None is silently skipped. Dot notation addresses nested keys.
Both methods compose freely and push to the same override list. Later calls take precedence.
Supported value types: String, &str, i64, i32, i8, u8, u32, f64, f32, bool.
Clap Adapter (optional)
Requires the
clapCargo feature (enabled by default). To use clapfig without clap:= { = "...", = false }
Config Subcommand
Add config management to your CLI by nesting clapfig::ConfigArgs:
use Subcommand;
use ;
This gives your users:
Custom Command Names
If the default subcommand or flag names conflict with your app (e.g. you already have a global --scope flag), use ConfigCommand to rename anything:
use ;
use ;
Available builder methods: list_name(), gen_name(), get_name(), set_name(), unset_name(), scope_long(), output_long(), output_short(). Default names match ConfigArgs exactly, so ConfigCommand::new() with no customization is equivalent to the derive path.
Template Generation
config gen produces a commented TOML file derived from your struct's /// doc comments:
# The host address to bind to.
# Default: "127.0.0.1"
#host = "127.0.0.1"
# The port number.
# Default: 8080
#port = 8080
[]
# Connection string URL.
#url =
# Connection pool size.
# Default: 10
#pool_size = 10
The template stays in sync with code — it's generated from the same struct. Change a doc comment or a default, the template reflects it.
Layer Precedence
Compiled defaults #[config(default = ...)]
↑ overridden by
Config files search paths in order, later paths win
↑ overridden by
Environment vars MYAPP__KEY
↑ overridden by
Overrides .cli_override()
Every layer is sparse. You only specify the keys you want to override. Unset keys fall through to the next layer down.
Persistence
config set <key> <value> writes to a named persist scope configured via .persist_scope() on the builder.
- Named scopes — each scope has a name (e.g. "local", "global") and a
SearchPath. The first scope added is the default for writes. - Auto-discovery — scope paths are automatically added to search paths, so persisted values are always discoverable in the merged view.
--scopeflag — target a specific scope:config set key val --scope global. Works withlist,get,set, andunset.- If the file exists, the key is patched in place using
toml_edit, preserving existing comments and formatting. - If the file doesn't exist, a fresh config is created from the generated template with the target key set.
- If no scopes are configured,
config setreturnsClapfigError::NoPersistPath.
Demo Application
The repo includes a runnable example that exercises every clapfig feature — nested config structs, file search paths, env vars, CLI overrides, and the config subcommand. It's a good starting point for integration and for ad-hoc testing.
# Print all resolved values (default color: yellow)
# Override via env var
CLAPFIG_DEMO__DISPLAY__COLOR=red
# Override via CLI flag
# Config subcommands
See examples/clapfig_demo/ for the full source.
Full Example (with clap)
use ;
use Config;
use ;
use ;
// -- Config struct --
// -- CLI --
// -- Main --