Sources
what sources do
Sources load a bag of KEY=VALUE pairs once, parse them, and serve all field lookups from that cached result. This is the right tool when your secret manager does bulk fetches — running the load command once gets you all the variables, and confit looks up individual fields without any extra subprocesses.
Compare to providers, which run a subprocess per key. If you have 8 Terraform outputs or 8 Infisical secrets in one section, providers spawn 8 subprocesses; a source spawns 1.
declaring a source
Add a [sources] section to confit.toml. The string shorthand is the load command:
[sources]
infisical = "infisical export --env={vars.stage} --format=dotenv"
The table form adds options:
[sources.infisical]
load = "infisical export --env={vars.stage} --format=dotenv"
secret = true
referencing source fields
Use source-name://FIELD_NAME as a value:
[vars]
stage = "dev"
[sources]
infisical = "infisical export --env={vars.stage} --format=dotenv"
[tf]
TF_VAR_neon_org_id = "infisical://NEON_ORGANIZATION_ID"
TF_VAR_db_password = "infisical://DB_PASSWORD"
TF_VAR_api_key = "infisical://API_KEY"
All three values are resolved from a single infisical export call. A missing field is a hard error — infisical://NOPE where NOPE isn’t in the output reports Field 'NOPE' not found in source 'infisical' rather than returning an empty string.
secret masking
Set secret = true on the source to mark every field from it as sensitive. Display commands (resolve, show, yaml) will mask them as *** by default; use --reveal to see real values. confit run always passes real values to the process.
[sources.vault]
load = "vault kv get -field=data -format=json secret/myapp | jq -r 'to_entries[] | \"\(.key)=\(.value)\""
secret = true
[app]
db_pass = "vault://DB_PASSWORD"
api_key = "vault://API_KEY"
You can also use the secret:// prefix on individual references to mark just that field:
[sources]
config = "myconfig export --format=dotenv"
[app]
public_name = "config://APP_NAME"
db_password = "secret://config://DB_PASSWORD"
Both compose: secret://source://FIELD marks the field as secret even when the source itself doesn’t have secret = true.
template variables
Source load commands support {vars.*} interpolation, the same as the rest of confit:
| Variable | Description |
|---|---|
{vars.stage} or {stage} |
Any variable from [vars], --set, or CONFIT_VAR_* |
The placeholders {path} and {uri} are not available — sources load a whole bag, so there’s no per-key path. If you need per-key substitution, use a provider instead.
output format
The load command must emit KEY=VALUE pairs, one per line. All of these formats are accepted:
FOO=bar
export FOO=bar
FOO="bar baz"
FOO='bar baz'
# comment lines and blank lines are ignored
This covers infisical export --format=dotenv, op environment read, and most other secret manager export commands out of the box.
the env:// built-in
env://VARNAME reads directly from the process environment — no declaration needed, no subprocess:
[app]
debug = "env://DEBUG"
db_host = "env://DATABASE_HOST"
An unset variable is a hard error: Environment variable 'DATABASE_HOST' is not set. This makes missing env vars loud rather than silently empty.
secret://env://VARNAME works as expected if you need to mask the value.
lazy loading
A source’s load command only runs if at least one key references it. Declaring a source in [sources] but not using any source://FIELD references in your config means the command never runs.
provider vs source — quick guide
| Provider | Source | |
|---|---|---|
| Command runs | once per key | once per source |
{path} in template |
✓ | ✗ |
| Good for | per-key lookups (Vault, SSM, 1Password items) | bulk exports (Infisical, op environment, .env exports) |
If your command needs to know which variable it’s fetching, use a provider. If it dumps everything and you pick fields, use a source.