Loading configuration and precedence rules

Version 0.6.0 Updated Nov 12, 2025

How loading works

The load_from_iter method (used by the convenience load) performs the following steps:

  1. Builds a figment configuration profile. A defaults provider constructed from the #[ortho_config(default = …)] attributes is added first.

  2. Attempts to load a configuration file. Candidate file paths are searched in the following order:

    1. If provided, a path supplied via the CLI flag generated by the discovery(...) attribute (which defaults to a hidden --config-path) or the <PREFIX>CONFIG_PATH environment variable (for example, APP_CONFIG_PATH or CONFIG_PATH) takes precedence; see Config path override.

    2. A dotfile named .<prefix>.toml in the current working directory.

    3. A dotfile of the same name in the user's home directory.

    4. On Unix‑like systems, the XDG configuration directory (e.g. ~/.config/app/config.toml) is searched using the xdg crate; on Windows, the %APPDATA% and %LOCALAPPDATA% directories are checked.

    5. If the json5 or yaml features are enabled, files with .json, .json5, .yaml, or .yml extensions are also considered in these locations.

  3. Adds an environment provider using the prefix specified on the struct. Keys are upper‑cased and nested fields use double underscores (__) to separate components.

  4. Adds a provider containing the CLI values (captured as Option<T> fields) as the final layer.

  5. Merges vector fields according to the merge_strategy (currently only append) so that lists of values from lower precedence sources are extended with values from higher precedence ones.

  6. Attempts to extract the merged configuration into the concrete struct. On success it returns the completed configuration; otherwise an OrthoError is returned.

Config path override

The derive macro always recognises a configuration override flag and the associated environment variables even when you do not declare a field explicitly. By default a hidden --config-path flag is accepted alongside <PREFIX>CONFIG_PATH and the unprefixed CONFIG_PATH. Applying the struct-level discovery(...) attribute customises this behaviour, allowing you to rename or expose the CLI flag and adjust the filenames searched during discovery:

#[derive(Debug, Deserialize, ortho_config::OrthoConfig)]
#[ortho_config(
    prefix = "APP_",
    discovery(
        app_name = "demo",
        env_var = "DEMO_CONFIG_PATH",
        config_file_name = "demo.toml",
        dotfile_name = ".demo.toml",
        project_file_name = ".demo.toml",
        config_cli_long = "config",
        config_cli_short = 'c',
        config_cli_visible = true,
    )
)]
struct CliArgs {
    #[ortho_config(default = 8080)]
    port: u16,
}

The snippet above exposes a visible --config/-c flag, renames the environment override to DEMO_CONFIG_PATH, and instructs discovery to search for demo.toml (and .demo.toml) within the standard directories. Omitting config_cli_visible keeps the flag hidden while still parsing it, and leaving config_cli_short unset skips the short alias. When the discovery(...) attribute is absent, the defaults—hidden --config-path, <PREFIX>CONFIG_PATH and CONFIG_PATH, and the automatically derived dotfile names—remain in effect.

Source precedence

Values are loaded from each layer in a specific order. Later layers override earlier ones. The precedence, from lowest to highest, is:

  1. Application‑defined defaults – values provided via default attributes or Option<T> fields are considered defaults.

  2. Configuration file – values from a TOML (or JSON5/YAML) file loaded from one of the paths listed above.

  3. Environment variables – variables prefixed with the struct's prefix (e.g. APP_PORT, APP_DATABASE__URL) override file values.

  4. Command‑line arguments – values parsed by clap override all other sources.

Nested structs are flattened in the environment namespace by joining field names with double underscores. For example, if AppConfig has a nested database field and the prefix is APP, then APP_DATABASE__URL sets the database.url field. If a nested struct has its own prefix attribute, that prefix is used for its fields (e.g. APP_DB_URL).

When clap's flatten attribute is employed to compose argument groups, the flattened struct is initialized even if no CLI flags within the group are specified. During merging, ortho_config discards these empty groups so that values from configuration files or the environment remain in place unless a field is explicitly supplied on the command line.

Using defaults and optional fields

Fields of type Option<T> are treated as optional values. If no source provides a value for an Option<T> field then it remains None. To provide a default value for a non‑Option field or for an Option<T> field that should have an initial value, specify #[ortho_config(default = expr)]. This default acts as the lowest‑precedence source and is overridden by file, environment or CLI values.

Environment variable naming

Environment variables are upper‑cased and use underscores. The struct‑level prefix (if supplied) is prepended without any separator, and nested fields are separated by double underscores. For the AppConfig and DatabaseConfig example above, valid environment variables include APP_LOG_LEVEL, APP_PORT, APP_DATABASE__URL and APP_DATABASE__POOL_SIZE. If the nested struct has its own prefix (DB), then the environment variable becomes APP_DB_URL.

Comma-separated values such as DDLINT_RULES=A,B,C are parsed as lists. The loader converts these strings into arrays before merging, so array fields behave the same across environment variables, CLI arguments and configuration files. Values containing literal commas must be wrapped in quotes or brackets to disable list parsing.