Skip to content

Configuration

A config block describes a CLI's settings: what they are called, what they hold, where a value can come from, and what to say about them in documentation. It is what usage g markdown renders as a settings reference, what a JSON schema for the config file is generated from, and what a runtime resolves values against.

kdl
config {
    prop "jobs" type="uint" default=0 help="Number of parallel jobs" {
        cli "--jobs" "-j"
        env "MYCLI_JOBS"
    }
}

Keys are dotted paths. prop blocks do not nest — write prop "status.missing_tools" rather than a status block containing a missing_tools one — because one spelling keeps merging, ordering and round-tripping unambiguous, and a schema generator can always re-nest on the dots.

Where values come from

Highest precedence first, and only the layers a CLI actually has:

  • the command line
  • the environment
  • config files, nearest first, with a .local variant outranking its base
  • the user's own configuration
  • files installed for the whole machine
  • the default a prop declares

A property can name its sources explicitly; the order they are written in is the order they are consulted.

kdl
config {
    prop "check" type="bool" {
        cli "--check"              // flags that set it, as declared elsewhere in this spec
        env "HK_CHECK" "HK_LINT"   // several: aliases, highest precedence first
        deprecated_env "HK_VERIFY" // read last, with a warning
        source "git" "hk.check"    // a source kind declared below
    }
}

source — kinds usage does not know about

usage reads the command line, the environment, and config files. Everything else — a git config, a pkl file, an .npmrc — is a kind the CLI reads itself and declares here so documentation can describe it. {key} and {value} are substituted.

kdl
config {
    source "git" name="git config" doc_hint="git config `{key}`" \
        set_hint="git config {key} {value}"
    source "pkl" name="hk.pkl"
}

Docs then render a property bound to that kind as "settable with git config hk.check", without usage having any idea what git is.

file — where config files live

In ascending precedence: the last one named wins. This is the chain that rc-style merging walks, and writing it down is what lets documentation describe it accurately.

kdl
config {
    file "/etc/mycli/config.toml" scope="system"
    file "~/.config/mycli/config.toml" scope="global"
    file "mycli.toml" findup=#true
    file "mycli.local.toml" findup=#true
    file ".myclirc" format="yaml"
}
propertymeaning
finduplook for this name in the current directory and every parent
scopeproject (default), global, or system
formatwhen the extension does not say: toml, json, yaml

scope is not decoration: a prop marked scope="global" refuses values from project files, so a setting a repository must not be able to change can say so.

prop — the settings

propertymeaning
typethe type, see below. Defaults to string
envan environment variable that sets it — the child node form below takes several
defaultthe value when nothing supplies one — a typed KDL value: 4, #true, "x"
optionalexplicitly permit or require absence; otherwise inferred from the type and default
default_notewhat to print instead of the default, when the real one needs explaining
help, long_helpone line, and the whole markdown story
help_headingthe section to list it under in generated docs
mergereplace (default), union for collections, deep for maps
scopeany (default), global (never from a project file), env (never from a file)
deprecatedwhy not to use it any more
deprecated_warn_at, deprecated_remove_atCLI versions that start warnings and stop accepting configured values
renamed_tothe property that replaces this one, so an old key folds into the new
hidekeep it out of docs and completions
sincethe version that introduced it
parsea named parser for one string: list_by_comma, list_by_colon, list_by_os_path_separator, set_by_comma
writes_towhere config set should write it, when that is not the usual file

And as child nodes, for anything multi-valued or long:

nodemeaning
cli "--jobs" "-j"flags that set it
env "A" "B"environment variables, highest precedence first
deprecated_env "OLD_A"environment aliases read last and reported as deprecated
alias "old.key"equivalent config keys, accepted without a deprecation warning
source "git" "a.b"its keys in a declared source kind
default "a" "b"a list default, values typed as written (default 80 443)
long_help "…"the long form, when a raw string reads better than a property
example "…"one invocation worth showing
choices { choice "a" help="…" }the values it accepts, each with its own help
x "ns.key" valuesee extensions

Deprecation gates

deprecated_warn_at withholds a setting's deprecation warning until the running CLI version reaches that release. At deprecated_remove_at, configured values for the setting are ignored and reported as removed; resolution continues, as it does for an unknown setting, and a declared default remains available to the compiled settings type.

The CLI version is runtime context, not the version of usage-config. Rust callers pass it explicitly with resolve_with_context and ResolutionContext::for_cli_version. The compatibility resolve entry point has no version context: it warns but keeps the value. An unreadable CLI version or milestone does the same, so uncertainty neither hides the deprecation nor silently changes configuration.

Versions follow the same rule as argv deprecations: dotted numeric segments support semver and calver, missing segments are zero, prereleases precede their release, and build metadata is ignored.

Types

base  := bool | string | int | uint | float | path | url | duration | object
type  := base | list<type> | set<type> | map<base, type> | option<type> | type "|" type

option<T> means absent is a legitimate state with no default standing in. A union — bool|string — records that a setting takes either; nothing here validates which.

A name this version of usage does not know is kept as written rather than refused, so a spec can name a type only its own tool understands and still load everywhere else. Anything consuming a type it cannot interpret treats it as a string.

For compatibility, data_type is still read as a spelling of type, and the older names (boolean, integer, number, usize, array<…>, optional<…>) are accepted.

Extensions

A tool often needs to carry something usage has no opinion about — which Rust type a setting deserializes into, which file a write should be routed to, an enterprise policy. x nodes hold it: preserved in order, written back out unchanged, present in usage g json, and interpreted by nothing in usage.

kdl
config {
    prop "python.uv_venv_auto" type="bool|string" {
        x "mise.rust_type" "PythonUvVenvAuto"
        x "mise.parse_env" "bool_string"
    }
}

This is the seam that lets a CLI with special rules describe its settings here without usage having to model those rules.

Completing settings

A config get/config set pair completes from the block without a run of its own — see complete:

kdl
complete "key" type="config_keys"
complete "value" type="config_values"

Splitting it out

A CLI with many settings does not want them inline. include reads another file, resolved relative to the one naming it:

kdl
name "mise"
bin "mise"
include file="./settings.usage.kdl"

Compatibility

The parser refuses vocabulary it does not know, so a spec using anything on this page must say which version of usage it needs:

kdl
min_usage_version "6.7.1"

Choose the oldest release that supports the nodes your spec uses; 6.7.1 is an example, not a requirement for every config block. The version declaration makes the requirement visible before an older consumer encounters unsupported syntax.

MIT LicenseCopyright © 2026jdx.dev