DevTools Hub

Search tools

Search for a developer tool

Environment

Environment Variable Resolver

Expand ${VAR} references between variables in the same .env-style set, chained across hops.

Part of the Environment Toolkit
Resolved
Variables
  • DATABASE_URLresolved

    ${DB_URL}postgres://user:pass@localhost:5432/mydb

    references: DB_URL

  • DB_URLresolved

    postgres://user:pass@${DB_HOST}:5432/mydbpostgres://user:pass@localhost:5432/mydb

    references: DB_HOST

  • DB_HOSTno references

    localhost

  • APP_URLresolved

    ${PROTOCOL}://${DOMAIN}https://example.com

    references: PROTOCOL, DOMAIN

  • PROTOCOLno references

    https

  • DOMAINno references

    example.com

  • PORTdefaulted

    ${PORT_NUM:-8080}8080

    references: PORT_NUM

  • API_KEYresolved

    ${SECRET_API_KEY:?SECRET_API_KEY must be set}sk_live_abc123

    references: SECRET_API_KEY

  • SECRET_API_KEYno references

    sk_live_abc123

What this does

Paste a set of KEY=VALUE lines where some values reference other keys in the same set — DATABASE_URL=${DB_URL} and DB_URL=postgres://... — and get every value fully resolved, chained across any number of hops. .env Validator flags this exact pattern as a warning, because plain dotenv genuinely doesn't expand it — that's deliberately the separate dotenv-expand package's job, which this tool models.

This is different from Compose Config Inspector, which resolves ${VAR} placeholders inside a docker-compose.yml against an external .env file — a variable there never references another variable in that same file. Here, the whole point is variables referencing each other within one set.

The interpolation syntax

SyntaxBehavior
${VAR}Plain substitution — empty string if VAR isn't defined anywhere in the set
${VAR:-default}Use the default if VAR is undefined or resolves empty
${VAR-default}Use the default only if VAR is completely undefined
${VAR:?message}Flag an error if VAR is undefined or resolves empty
${VAR?message}Flag an error only if VAR is completely undefined
$$Escaped to a literal $

Why a broken variable doesn't block the rest

A circular reference or a missing :? value only marks that variable (and anything that references it, transitively) as broken — every independent variable still resolves normally. That matches how a real chained expansion actually behaves at runtime: one bad reference doesn't stop your whole app from starting, it just leaves that one value wrong.

The self-reference gotcha

PATH=${PATH}:/usr/local/bin is a genuinely common pattern in a real shell — it extends whatever PATH the shell already had. Inside a single .env-style set with no outside process to fall back to, that same line is a direct self-reference and gets flagged as circular here, since there's no "existing shell value" for it to extend. If a variable is meant to build on a value from outside this file, that's exactly what Compose Config Inspector's shell overrides are for.

FAQ

Does this match how dotenv-expand actually resolves things?

It models the same core behavior — chained substitution across any number of hops, with circular references caught rather than hanging. For a definitive answer on a specific project, run dotenv-expand itself.

Why is my variable empty instead of showing an error?

Plain ${VAR} (no :-/:?) silently resolves to an empty string when VAR is undefined — that's the correct, if surprising, behavior to model, not a bug in this tool. Use ${VAR:?message} if a missing value should actually be flagged.

Is anything uploaded anywhere?

No — parsing and resolution both run entirely in your browser.

Try it yourself

.env Validator checks the raw file for dotenv parsing gotchas before resolution even matters, and Environment Variable Diff compares two already-resolved sets. Both run entirely in your browser.

Related tools