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}/graphql

When you retrieve secrets, references are resolved:

redenv view AUTH_URL
# Output: https://api.example.com/auth

Change 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#

RuleExampleResult
Simple reference${BASE_URL}/apiResolves BASE_URL
Multiple references${PROTOCOL}://${HOST}:${PORT}Resolves all three
Inline referenceHello ${USER}, welcomeResolves USER in place
Case-sensitive${base_url} won't match BASE_URLLeft unexpanded
Allowed charactersLetters, 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}/v1
API_URL → https://api.example.com/v1

The 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 placeholder

When loaded, the escaped reference is returned as a literal string:

TEMPLATE → ${BASE_URL} is a placeholder

Backslash Rules#

Backslashes before ${...} follow a consistent pattern based on count:

Stored valueBackslashesRuleExpanded result
${VAR}0even — expandvalue of VAR
\${VAR}1odd — escape${VAR}
\\${VAR}2even — expand\ + value of VAR
\\\${VAR}3odd — escape\${VAR}
\\\\${VAR}4even — expand\\ + value of VAR
\\\\\${VAR}5odd — escape\\${VAR}

The rule: every pair of \\ becomes a single literal \. A remaining lone \ before ${ escapes the reference.

  • Even backslash count → n/2 literal 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 -> A

Self-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 yet
API_URL → ${BASE_URL}/api

Info

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:

app.ts
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}/api
app.py
secrets = await client.load()

print(secrets["API_URL"])      # Expanded: https://api.example.com/api
print(secrets.raw["API_URL"])  # Raw: ${BASE_URL}/api

CLI Expansion Behavior#

Three CLI commands expand references automatically:

CommandBehavior
redenv view <key>Shows original and expanded value side-by-side if they differ
redenv listDisplays expanded values for all secrets
redenv exportWrites expanded values to .env file

To export without expansion, use the --raw flag:

redenv export --raw

If 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}/data

API 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#

  1. Use the interactive prompt for references — don't pass ${...} values as inline arguments
  2. Use clear prefixes for base valuesBASE_URL, ROOT_PATH signal that other secrets depend on them
  3. Keep reference depth shallow — 3-4 levels max for readability and debuggability
  4. Test expansions before production — use redenv view to verify resolved values
  5. Use --raw for debuggingredenv export --raw shows exactly what's stored