Advanced

Variable Templating

Reference other variables with ${VAR} interpolation and resolve them on pull.

Back to all guides

Variable templating lets one secret reference another using ${VAR} syntax, so you can compose values instead of repeating them. The dotEnv CLI resolves these references locally when you ask it to, which keeps your stored secrets DRY and your generated .env files fully expanded.

Basic interpolation

Reference another variable with ${NAME} or the bare $NAME form. Resolution is recursive, so a reference can itself contain references:

stored secrets
HOST=api.example.com
PORT=8080
BASE_URL=https://${HOST}:${PORT}
HEALTHCHECK_URL=${BASE_URL}/healthz

Resolve on pull

By default the CLI leaves references intact. Pass --resolve (or -r) to expand them during a pull or export:

terminal
dotenv pull my-app/production/api --resolve

With --resolve, the secrets above expand to:

resolved output
HOST=api.example.com
PORT=8080
BASE_URL=https://api.example.com:8080
HEALTHCHECK_URL=https://api.example.com:8080/healthz

Defaults and conditional values

Beyond plain substitution, the interpolator understands three shell-style operators inside the braces:

Syntax Meaning
${VAR:-default}Use VAR if it is set, otherwise fall back to default.
${VAR:+alt}Use alt if VAR is set, otherwise an empty string.
${VAR:?message}Fail with message if VAR is not set.
operators in action
# Fall back to a sensible default
LOG_LEVEL=${LOG_LEVEL:-info}
# Only set a flag when a feature toggle exists
DEBUG_BANNER=${DEBUG:+enabled}
# Refuse to build without a required value
DATABASE_URL=${DATABASE_URL:?database url is required}

The operand of an operator can itself reference other variables, so defaults can be composed too.

Unresolved and missing variables

If a referenced variable is not set, the CLI keeps the original ${VAR} text in the output rather than emitting an empty value, so nothing silently disappears. The :? operator is the way to turn a missing required value into a hard error instead.

Circular references

The interpolator detects cycles. If two variables reference each other, resolution stops with a clear "circular reference detected" error rather than looping forever:

this is rejected
A=${B}
B=${A} # error: circular reference detected

When to resolve

Keep references unresolved in storage so a change to HOST automatically flows into BASE_URL everywhere. Resolve only at the point of consumption (when you pull or export the final .env for a deploy) by adding --resolve. Combine this with Environment Inheritance to share a base value across levels and reference it from each.