Secret Expansion
Use variable references to compose secrets, reduce duplication, and maintain consistency across your configuration.
Secret Expansion lets you reference one secret inside another using ${VAR_NAME} syntax. Change the base value once and every dependent secret updates automatically — no manual edits needed.
Info
Expansion happens at runtime when secrets are loaded, not when they're stored. The encrypted value in Redis contains the raw reference (${BASE_URL}/api), not the resolved result.
Basic Usage#
# Set a base value
redenv add BASE_URL "https://api.example.com"
# Reference it in other secrets
redenv add AUTH_URL
› ${BASE_URL}/auth
redenv add WEBHOOK_URL
› ${BASE_URL}/webhooks
redenv add GRAPHQL_URL
› ${BASE_URL}/graphqlWhen you retrieve secrets, references are resolved:
redenv view AUTH_URL
# Output: https://api.example.com/authChange BASE_URL and all dependent secrets update on the next load.
Info
Values containing ${...} references must be entered through the interactive prompt. If you pass a reference as an inline argument, the CLI will detect it and switch to interactive mode automatically.
Syntax Rules#
| Rule | Example | Result |
|---|---|---|
| Simple reference | ${BASE_URL}/api | Resolves BASE_URL |
| Multiple references | ${PROTOCOL}://${HOST}:${PORT} | Resolves all three |
| Inline reference | Hello ${USER}, welcome | Resolves USER in place |
| Case-sensitive | ${base_url} won't match BASE_URL | Left unexpanded |
| Allowed characters | Letters, digits, underscores only | ${API_KEY_V2} works |
| Invalid names | ${BASE-URL}, ${API.KEY} | Won't be recognized |
Nested References#
References can chain through multiple levels:
redenv add PROTOCOL "https"
redenv add HOST "api.example.com"
redenv add BASE_URL
› ${PROTOCOL}://${HOST}
redenv add API_URL
› ${BASE_URL}/v1API_URL → https://api.example.com/v1The expansion engine resolves all levels recursively and caches each resolved key to avoid redundant work.
Escaping#
To store a literal ${...} without Redenv expanding it, add a single backslash prefix:
redenv add TEMPLATE
› \${BASE_URL} is a placeholderWhen loaded, the escaped reference is returned as a literal string:
TEMPLATE → ${BASE_URL} is a placeholderBackslash Rules#
Backslashes before ${...} follow a consistent pattern based on count:
| Stored value | Backslashes | Rule | Expanded result |
|---|---|---|---|
${VAR} | 0 | even — expand | value of VAR |
\${VAR} | 1 | odd — escape | ${VAR} |
\\${VAR} | 2 | even — expand | \ + value of VAR |
\\\${VAR} | 3 | odd — escape | \${VAR} |
\\\\${VAR} | 4 | even — expand | \\ + value of VAR |
\\\\\${VAR} | 5 | odd — escape | \\${VAR} |
The rule: every pair of \\ becomes a single literal \. A remaining lone \ before ${ escapes the reference.
- Even backslash count →
n/2literal backslashes + reference is expanded - Odd backslash count →
floor(n/2)literal backslashes + reference is kept as literal
Circular Dependency Detection#
Redenv detects and rejects circular references at load time:
redenv add A
› ${B}
redenv add B
› ${A}Error: Circular dependency detected: A -> B -> ASelf-references (A referencing ${A}) are caught the same way.
Missing References#
If a referenced secret doesn't exist, the reference is left as-is — no error is thrown:
redenv add API_URL
› ${BASE_URL}/api
# BASE_URL is not set yetAPI_URL → ${BASE_URL}/apiInfo
This is intentional — it lets you define references before their dependencies exist. Once BASE_URL is added, API_URL resolves on the next load.
Accessing Raw Values#
Both SDKs expose a .raw property with the unexpanded values:
const secrets = await redenv.load();
console.log(secrets.API_URL); // Expanded: https://api.example.com/api
console.log(secrets.raw.API_URL); // Raw: ${BASE_URL}/apisecrets = await client.load()
print(secrets["API_URL"]) # Expanded: https://api.example.com/api
print(secrets.raw["API_URL"]) # Raw: ${BASE_URL}/apiCLI Expansion Behavior#
Three CLI commands expand references automatically:
| Command | Behavior |
|---|---|
redenv view <key> | Shows original and expanded value side-by-side if they differ |
redenv list | Displays expanded values for all secrets |
redenv export | Writes expanded values to .env file |
To export without expansion, use the --raw flag:
redenv export --rawIf a circular dependency is detected during export, Redenv falls back to raw values automatically with a warning.
Use Cases#
Database Connection Strings#
redenv add DB_HOST "postgres.example.com"
redenv add DB_PORT "5432"
redenv add DB_NAME "myapp_prod"
redenv add DB_USER "app_user"
redenv add DB_PASS "secret123"
redenv add DATABASE_URL
› postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}Change any component and DATABASE_URL updates automatically.
Multi-Environment URLs#
# In development
redenv add API_BASE "http://localhost:3000"
# In production
redenv add API_BASE "https://api.prod.com"
# Same references work in both environments
redenv add AUTH_ENDPOINT
› ${API_BASE}/auth
redenv add DATA_ENDPOINT
› ${API_BASE}/dataAPI Versioning#
redenv add API_VERSION "v2"
redenv add API_BASE "https://api.example.com"
redenv add API_URL
› ${API_BASE}/${API_VERSION}Update API_VERSION once and all services pick up the change.
Best Practices#
- Use the interactive prompt for references — don't pass
${...}values as inline arguments - Use clear prefixes for base values —
BASE_URL,ROOT_PATHsignal that other secrets depend on them - Keep reference depth shallow — 3-4 levels max for readability and debuggability
- Test expansions before production — use
redenv viewto verify resolved values - Use
--rawfor debugging —redenv export --rawshows exactly what's stored