configorama
v0.9.15
Published
Variable support for configuration files
Downloads
4,300
Readme
Configorama
Dynamic configuration values with variable support for yml, json, toml, hcl (Terraform), and other config formats.
Configorama extends your configuration with a powerful variable system that resolves values from CLI options, environment variables, file references, TypeScript/JavaScript files, git data, self-references, conditional expressions, and any custom source you define.
Key Features
- Multiple file formats - yml, yaml, json, toml, ini, hcl (Terraform), TypeScript, JavaScript, markdown
- Rich variable sources - env vars, CLI flags, file refs, git data, cron expressions, eval/if expressions
- Async/sync function execution - Import and execute JavaScript/TypeScript files with argument passing
- Self-referencing - Reference other values within the same config using dot notation
- Custom variable sources - Pluggable architecture to add your own variable resolvers
- Filters and functions - Transform and combine values with built-in or custom operators
- Metadata extraction - Analyze configs without resolving them, or get full resolution history
- Circular dependency detection - Helpful error messages instead of infinite loops
- TypeScript support - Full type definitions and TypeScript file execution via tsx/ts-node
Table of Contents
- Getting Started
- How It Works
- Variable Sources
- API Reference
- Configuration Options
- Custom Variable Sources
- CLI Usage
- Testing
- Deployment
- Troubleshooting
- FAQ
- Advanced Usage
- What's New
- Alternative Libraries
- Inspiration
Getting Started
Installation
As a library dependency:
npm install configoramaAs a global CLI tool:
npm install -g configoramaQuick Start
Async API (recommended for most use cases):
const path = require('path')
const configorama = require('configorama')
const cliFlags = require('minimist')(process.argv.slice(2))
// Path to yaml/json/toml config
const myConfigFilePath = path.join(__dirname, 'config.yml')
// Execute config resolution asynchronously
const config = await configorama(myConfigFilePath, { options: cliFlags })
console.log(config) // resolved configSync API (for synchronous execution contexts):
const path = require('path')
const configorama = require('configorama')
const cliFlags = require('minimist')(process.argv.slice(2))
// Path to yaml/json/toml config
const myConfigFilePath = path.join(__dirname, 'config.yml')
// Execute config resolution synchronously
const config = configorama.sync(myConfigFilePath, { options: cliFlags })
console.log(config) // resolved configExample configuration file (config.yml):
# Environment variable
apiKey: ${env:API_KEY}
# CLI option (e.g., --stage prod)
environment: ${opt:stage, 'dev'}
# Self-reference to other values
service: my-app
fullName: ${service}-api
# File reference
secrets: ${file(./secrets.yml)}
# Git information
branch: ${git:branch}
commit: ${git:sha1}
# Conditional logic
memorySize: ${if(${environment} === 'prod' ? 1024 : 512)}
# Nested references
database:
host: ${env:DB_HOST, 'localhost'}
port: ${env:DB_PORT, 5432}
name: ${service}-${environment}Running Examples
The project includes example files demonstrating various features:
# Clone the repository
git clone https://github.com/DavidWells/configorama
cd configorama
# Install dependencies
npm install
# Run async API example
node examples/using-async-api.js --stage prod
# Run sync API example
node examples/using-sync-api.js --stage dev
# Run zero-config example
node examples/zero-config.js
# Run TypeScript example
node examples/typescript/using-typescript.jsHow It Works
Resolution Flow
Configorama creates a dependency graph of your config file and all its dependencies, then resolves values based on their variable sources. The resolution process follows this flow:
flowchart TD
A[Load config file] --> B[Parse yml/json/toml/hcl to object]
B --> C[Preprocess: raw config file]
C --> D{Return metadata only?}
D -->|Yes| E[Collect variable metadata]
E --> F[Return found variable metadata + original config]
D -->|No| G[Traverse & resolve variables recursively]
G --> H[Post-process: runs filters and functions]
H --> I[Return resolved config]Resolution process:
- Load - Read config file from disk or accept JavaScript object
- Parse - Convert to JavaScript object (format auto-detected by extension)
- Preprocess - Identify all variables and build dependency graph
- Traverse - Recursively resolve variables in dependency order
- Post-process - Apply filters and functions
- Return - Fully resolved configuration object
Analyzing Without Resolving
Analyze config structure and variables without actually resolving them:
const result = await configorama.analyze('config.yml')
// Returns metadata about variables without resolving them
console.log(result.originalConfig) // Raw config object
console.log(result.variables) // All variables found
console.log(result.uniqueVariables) // Variables grouped by name
console.log(result.fileDependencies) // File references foundUse cases:
- Validate config structure before deployment
- Generate documentation of required environment variables
- Build dependency graphs for complex configs
- Audit what external resources a config depends on
Getting Metadata
Resolve config and get detailed metadata about the resolution process:
const result = await configorama('config.yml', {
returnMetadata: true,
options: { stage: 'prod' }
})
// Returns both resolved config and metadata
console.log(result.config) // Fully resolved config
console.log(result.originalConfig) // Raw config object
console.log(result.metadata.variables) // Variable info with resolution details
console.log(result.metadata.fileDependencies) // All file dependencies
console.log(result.metadata.summary) // { totalVariables, requiredVariables, variablesWithDefaults }
console.log(result.resolutionHistory) // Step-by-step resolution for each pathMetadata structure:
{
config: { /* resolved config */ },
originalConfig: { /* raw config */ },
metadata: {
variables: [
{
variable: '${env:API_KEY}',
variableType: 'env',
variableName: 'API_KEY',
variablePath: 'apiKey',
defaultValue: null,
hasDefault: false,
resolved: true,
resolvedValue: 'secret-key-123'
},
// ... more variables
],
summary: {
totalVariables: 15,
requiredVariables: 8,
variablesWithDefaults: 7
},
fileDependencies: ['./secrets.yml', './config.ts']
},
resolutionHistory: {
'apiKey': [
{ step: 1, value: '${env:API_KEY}', type: 'env' },
{ step: 2, value: 'secret-key-123', resolved: true }
]
}
}Variable Sources
Configorama supports multiple variable sources out of the box. All variable syntax follows the pattern ${type:value} or ${type(value)}.
Summary Table
| Variable | Syntax | Description | Example |
|----------|-----------------------|------------------------|---------|
| env | ${env:VAR} | Environment variables | ${env:NODE_ENV} |
| opt | ${opt:flag} | CLI option flags | ${opt:stage} |
| param | ${param:key} | Parameter values | ${param:domain} |
| self | ${key} or ${self:key} | Self references | ${database.host} |
| file | ${file(path)} | File references | ${file(./secrets.yml)} |
| text | ${text(path)} | Raw text file | ${text(./README.md)} |
| git | ${git:value} | Git data | ${git:branch} |
| cron | ${cron(expr)} | Cron expressions | ${cron('every 5 minutes')} |
| eval | ${eval(expr)} | Math/logic expressions | ${eval(10 + 5)} |
| if | ${if(expr)} | Conditional expressions| ${if(x > 5 ? 'yes' : 'no')} |
Environment Variables
Access values from process.env environment variables.
# Basic env var
apiKey: ${env:SECRET_KEY}
# With fallback default if env var not found
apiKeyWithFallback: ${env:SECRET_KEY, 'defaultApiKey'}
# Common patterns
nodeEnv: ${env:NODE_ENV, 'development'}
port: ${env:PORT, 3000}
debug: ${env:DEBUG, false}How it works:
- Reads from
process.envat resolution time - Supports default values with comma syntax
- Throws error if env var not found and no default provided (unless
allowUnresolvedVariablesis set)
CLI usage:
# Set env var then run
SECRET_KEY=abc123 node app.js
# Or export first
export SECRET_KEY=abc123
node app.jsCLI Option Flags
Access values from command line arguments passed via the options parameter.
# CLI option. Example `cmd --stage dev` makes `bar: dev`
bar: ${opt:stage}
# Composed example makes `foo: dev-hello`
foo: ${opt:stage}-hello
# With default value. If no --stage flag, uses 'dev'
environment: ${opt:stage, 'dev'}
# Boolean flags
verbose: ${opt:verbose, false}
# Nested paths
region: ${opt:aws.region, 'us-east-1'}How it works:
- Reads from the
optionsobject passed to configorama - Typically populated from CLI args using
minimistor similar parser - Supports dot-notation for nested option paths
Example:
const minimist = require('minimist')
const configorama = require('configorama')
const argv = minimist(process.argv.slice(2))
// argv = { stage: 'prod', verbose: true, aws: { region: 'eu-west-1' } }
const config = await configorama('config.yml', { options: argv })# Command line
node app.js --stage prod --verbose --aws.region eu-west-1Parameter Values
Access parameter values via ${param:key}. Parameters follow a resolution hierarchy:
- CLI params (
--param="key=value") - highest priority - Stage-specific params (
stages.<stage>.params) - Default params (
stages.default.params)
# Direct parameter reference
appDomain: ${param:domain}
# Parameter with fallback
apiKey: ${param:apiKey, 'default-api-key'}
# Stage-specific parameters defined in config
stages:
dev:
params:
domain: dev.myapp.com
dbHost: localhost
prod:
params:
domain: myapp.com
dbHost: prod-db.myapp.com
default:
params:
domain: default.myapp.com
dbPort: 3306CLI Usage:
# Single param
node app.js --param="domain=example.com"
# Multiple params
node app.js --param="domain=example.com" --param="apiKey=secret123"
# With stage selection
node app.js --stage prod --param="domain=cli-override.com"Code Usage:
const config = await configorama('config.yml', {
options: {
stage: 'prod',
param: ['domain=cli-override.com', 'apiKey=secret']
}
})Resolution order example:
stages:
prod:
params:
domain: prod.myapp.com # 2. Stage-specific
default:
params:
domain: default.myapp.com # 3. Default fallback
appUrl: ${param:domain}# CLI override (highest priority)
node app.js --stage prod --param="domain=cli.myapp.com"
# Result: appUrl = 'cli.myapp.com'
# Stage param (no CLI override)
node app.js --stage prod
# Result: appUrl = 'prod.myapp.com'
# Default param (no CLI override, no stage match)
node app.js --stage staging
# Result: appUrl = 'default.myapp.com'Self References
Reference values from other key paths in the same configuration file using dot notation.
foo: bar
zaz:
matazaz: 1
wow:
cool: 2
# Shorthand dot.prop reference
two: ${foo} # Resolves to 'bar'
# Explicit self file reference
one: ${self:foo} # Resolves to 'bar'
# Dot prop reference traverses objects
three: ${zaz.wow.cool} # Resolves to 2
# Complex nested references
database:
host: localhost
port: 5432
name: mydb
connectionString: postgres://${database.host}:${database.port}/${database.name}
# Resolves to: postgres://localhost:5432/mydb
# Array access
items:
- first
- second
- third
selectedItem: ${items[1]} # Resolves to 'second'How it works:
- Uses dot-notation for nested object access
- Supports array index access with bracket notation
- Resolves in dependency order (referenced values resolved first)
- Detects circular references and throws helpful errors
File References
Import values from external yml, json, toml, hcl, or other supported files by relative path.
# Import full yml/json/toml/hcl file via relative path
fileRef: ${file(./subFile.yml)}
# Import sub values from files (topLevel key from other-config.yml)
fileValue: ${file(./other-config.yml):topLevel}
# Import nested sub values (nested.value from other-config.json)
fileValueSubKey: ${file(./other-config.json):nested.value}
# Fallback to default value if file not found
fallbackValueExample: ${file(./not-found.yml), 'fall back value'}
# Relative paths from config file location
secrets: ${file(../shared/secrets.yml)}
# Import from subdirectory
dbConfig: ${file(./config/database.yml):production}Supported file types (extensions are case-insensitive):
| Type | Extensions |
|------|------------|
| TypeScript | .ts, .tsx, .mts, .cts |
| JavaScript | .js, .cjs |
| ESM | .mjs, .esm |
| YAML | .yml, .yaml |
| TOML | .toml, .tml |
| INI | .ini |
| JSON | .json, .json5, .jsonc |
| HCL (Terraform) | .tf, .hcl, .tf.json |
| Markdown | .md, .mdx, .markdown, .mdown, .mkdn, .mkd |
Path resolution:
- Relative paths resolved from config file's directory
- Absolute paths supported
~home directory expansion NOT supported (use absolute paths)
Example file structure:
project/
├── config.yml # Main config
├── secrets.yml # Secrets file
└── environments/
├── dev.yml
└── prod.yml# config.yml
secrets: ${file(./secrets.yml)}
environment: ${file(./environments/${opt:stage}.yml)}Sync/Async File References
Execute JavaScript files and use their exported function's return value. Functions can be synchronous or asynchronous and receive arguments from your config.
# Async function execution
asyncJSValue: ${file(./async-value.js)}
# Sync function execution
syncJSValue: ${file(./sync-value.js)}
# With arguments (resolved before being passed)
secrets: ${file(./fetch-secrets.js, ${self:environment}, ${self:region})}JavaScript file example (async-value.js):
async function fetchSecretsFromRemoteStore() {
// Simulate async operation (AWS Secrets Manager, HashiCorp Vault, etc.)
await new Promise(resolve => setTimeout(resolve, 1000))
return {
apiKey: 'secret-key-123',
dbPassword: 'db-password-456'
}
}
module.exports = fetchSecretsFromRemoteStoreSync function example (sync-value.js):
function getEnvironmentConfig() {
return {
timeout: 5000,
retries: 3,
logLevel: process.env.NODE_ENV === 'production' ? 'error' : 'debug'
}
}
module.exports = getEnvironmentConfigPassing Arguments to Functions
You can pass resolved values from your config as arguments to JavaScript/TypeScript functions:
foo: bar
baz:
qux: quux
# Pass resolved values as arguments
secrets: ${file(./fetch-secrets.js, ${self:foo}, ${self:baz})}Arguments are passed in order, with the config context always last:
/**
* @param {string} foo - First arg from YAML ('bar')
* @param {object} baz - Second arg from YAML ({ qux: 'quux' })
* @param {import('configorama').ConfigContext} ctx - Config context (always last)
*/
async function fetchSecrets(foo, baz, ctx) {
console.log(foo) // 'bar'
console.log(baz) // { qux: 'quux' }
// Access config context
console.log(ctx.originalConfig) // Original unresolved config
console.log(ctx.currentConfig) // Current partially-resolved config
console.log(ctx.options) // Options passed to configorama
return { secret: 'value' }
}
module.exports = fetchSecretsConfigContext
The ctx parameter (always the last argument) provides access to:
| Property | Description |
|----------|-------------|
| originalConfig | The original unresolved configuration object |
| currentConfig | The current (partially resolved) configuration |
| options | Options passed to configorama (populates ${opt:xyz} variables) |
TypeScript users can import the type:
import type { ConfigContext } from 'configorama'
async function fetchSecrets(
foo: string,
baz: { qux: string },
ctx: ConfigContext
): Promise<string> {
// Full type support for ctx properties
return 'secret-value'
}
export = fetchSecretsFunctions Without Arguments
If you don't need arguments, the function still receives ctx as its only parameter:
// No args - ctx is the only parameter
async function getSecrets(ctx) {
return ctx.options.stage === 'prod'
? 'prod-secret'
: 'dev-secret'
}
module.exports = getSecretsTypeScript File References
Execute TypeScript files using tsx (recommended) or ts-node.
Installation:
# Recommended: Modern, fast TypeScript execution
npm install tsx --save-dev
# Alternative: Traditional ts-node approach
npm install ts-node typescript --save-devUsage in config:
# TypeScript configuration object
config: ${file(./config.ts)}
# TypeScript async function
secrets: ${file(./async-secrets.ts)}
# Specific property from TypeScript export
database: ${file(./config.ts):database}
# With arguments
apiConfig: ${file(./config.ts, ${opt:stage})}TypeScript Object Export (typescript-config.ts):
interface DatabaseConfig {
host: string
port: number
database: string
ssl: boolean
}
interface ApiConfig {
baseUrl: string
timeout: number
retries: number
}
interface ConfigObject {
environment: string
database: DatabaseConfig
api: ApiConfig
features: {
enableNewFeature: boolean
debugMode: boolean
}
}
function createConfig(): ConfigObject {
return {
environment: process.env.STAGE || 'development',
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'myapp',
ssl: process.env.NODE_ENV === 'production'
},
api: {
baseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
timeout: 5000,
retries: 3
},
features: {
enableNewFeature: process.env.STAGE === 'production',
debugMode: process.env.DEBUG === 'true'
}
}
}
export = createConfigTypeScript Async Function (typescript-async.ts):
interface SecretStore {
apiKey: string
dbPassword: string
jwtSecret: string
}
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function fetchSecretsFromVault(): Promise<SecretStore> {
console.log('Fetching secrets from vault...')
// Simulate async operations (AWS Secrets Manager, HashiCorp Vault, etc.)
await delay(100)
return {
apiKey: process.env.API_KEY || 'dev-api-key',
dbPassword: process.env.DB_PASSWORD || 'dev-password',
jwtSecret: process.env.JWT_SECRET || 'dev-jwt-secret'
}
}
export = fetchSecretsFromVaultComplete Example Configuration:
# config-with-typescript.yml
service: my-awesome-app
# Load configuration from TypeScript file
provider: ${file(./typescript-config.ts)}
# Load secrets asynchronously from TypeScript file
secrets: ${file(./typescript-async.ts)}
# Mix TypeScript with other configuration
custom:
stage: ${opt:stage, "dev"}
region: ${opt:region, "us-east-1"}
# Use TypeScript files for specific sections
databaseConfig: ${file(./typescript-config.ts):database}
# Environment-specific overrides
stageVariables:
dev:
logLevel: debug
prod:
logLevel: info
# Regular configuration values
resources:
description: "Configuration loaded with TypeScript support"
functions:
hello:
handler: handler.hello
environment:
LOG_LEVEL: ${self:custom.stageVariables.${self:custom.stage}.logLevel}
DB_HOST: ${self:provider.database.host}
API_KEY: ${self:secrets.apiKey}Features:
- Modern tsx execution (fast, no compilation) with ts-node fallback
- Support for both sync and async TypeScript functions
- Function argument passing via config variables
- Full TypeScript interface support
- Comprehensive error handling with helpful dependency messages
Terraform HCL Support
Configorama supports Terraform HCL (HashiCorp Configuration Language) files, allowing you to parse .tf, .tf.json, and .hcl files.
Installation:
HCL parsing requires the optional @cdktf/hcl2json package:
npm install @cdktf/hcl2jsonSupported file types:
.tf- Terraform configuration files.hcl- Generic HCL files.tf.json- Terraform JSON configuration files
Example:
const configorama = require('configorama')
// Parse a Terraform configuration file
const terraformConfig = await configorama('./main.tf')
// Access Terraform variables, resources, locals, etc.
console.log(terraformConfig.variable) // Variables defined in the file
console.log(terraformConfig.resource) // Resources
console.log(terraformConfig.locals) // Local values
console.log(terraformConfig.output) // OutputsImporting Terraform files:
# Import Terraform variables from a .tf file
terraformVars: ${file(./terraform/variables.tf)}
# Import specific variable from Terraform file
region: ${file(./terraform/variables.tf):variable.region[0].default}Variable syntax:
When loading .tf or .hcl files directly, configorama automatically uses $[...] syntax instead of ${...} to avoid conflicts with Terraform's native ${var.name} interpolation. Terraform expressions like ${var.environment} and ${map(string)} are preserved as-is.
// Loading .tf directly - uses $[...] syntax automatically
const config = await configorama('./main.tf')
// config.locals[0].app_name = "myapp-${var.environment}" (preserved)
// Use $[...] for configorama variables in .tf files
// myvar: $[env:MY_VAR]
// myref: $[file(./other.yml)] # referenced files also use $[...]When importing .tf files from other config formats (yml, json, etc.) via ${file()}, the parent file's syntax applies. Use allowUnknownVariableTypes: true if the imported .tf contains Terraform interpolations:
const config = await configorama('./config.yml', {
allowUnknownVariableTypes: true
})Read-only support:
Currently, HCL files can be read and parsed, but writing/generating HCL files is not supported.
See tests/hclTests for example Terraform files.
Git References
Access repository information from the current working directory's git data.
########################
# Git Variables
########################
# Repo owner/name. E.g. DavidWells/configorama
repo: ${git:repo}
repository: ${git:repository}
# Repo owner. E.g. DavidWells
owner: ${git:owner}
repoOwner: ${git:repoOwner}
repoOwnerDashed: ${git:repo-owner}
# Url. E.g. https://github.com/DavidWells/configorama
url: ${git:url}
repoUrl: ${git:repoUrl}
repoUrlDashed: ${git:repo-url}
# Directory. E.g. https://github.com/DavidWells/configorama/tree/master/tests/gitVariables
dir: ${git:dir}
directory: ${git:directory}
# Branch
branch: ${git:branch}
# Commits. E.g. 785fa6b982d67b079d53099d57c27fa87c075211
commit: ${git:commit}
# Sha1. E.g. 785fa6b
sha1: ${git:sha1}
# Message. E.g. 'Initial commit'
message: ${git:message}
# Remotes. E.g. https://github.com/DavidWells/configorama
remote: ${git:remote}
remoteDefined: ${git:remote('origin')}
remoteDefinedNoQuotes: ${git:remote(origin)}
# Tags. E.g. v0.5.2-1-g785fa6b
tag: ${git:tag}
# Describe. E.g. v0.5.2-1-g785fa6b
describe: ${git:describe}
# Timestamp. E.g. 2025-01-28T07:28:53.000Z
gitTimestampRelativePath: ${git:timestamp('../../package.json')}
# Timestamp. E.g. 2025-01-28T07:28:53.000Z
gitTimestampAbsolutePath: ${git:timestamp('package.json')}How it works:
- Reads git data from
.gitdirectory in current working directory or parent directories - Executes git commands via child process
- Throws error if not in a git repository
Cron Values
Convert human-readable time expressions into standard cron syntax.
# Basic patterns
everyMinute: ${cron('every minute')} # * * * * *
everyHour: ${cron('every hour')} # 0 * * * *
everyDay: ${cron('every day')} # 0 0 * * *
weekdays: ${cron('weekdays')} # 0 0 * * 1-5
midnight: ${cron('midnight')} # 0 0 * * *
noon: ${cron('noon')} # 0 12 * * *
# Interval patterns
every5Minutes: ${cron('every 5 minutes')} # */5 * * * *
every15Minutes: ${cron('every 15 minutes')} # */15 * * * *
every2Hours: ${cron('every 2 hours')} # 0 */2 * * *
every3Days: ${cron('every 3 days')} # 0 0 */3 * *
# Specific times
at930: ${cron('at 9:30')} # 30 9 * * *
at930pm: ${cron('at 9:30 pm')} # 30 21 * * *
at1200: ${cron('at 12:00')} # 0 12 * * *
at1230am: ${cron('at 12:30 am')} # 30 0 * * *
# Weekday patterns
mondayMorning: ${cron('on monday at 9:00')} # 0 9 * * 1
fridayEvening: ${cron('on friday at 17:00')} # 0 17 * * 5
sundayNoon: ${cron('on sunday at 12:00')} # 0 12 * * 0
# Pre-existing cron expressions (pass through)
customCron: ${cron('15 2 * * *')} # 15 2 * * *Supported expressions:
every N minutes/hours/daysat HH:MM [am/pm]on [weekday] at HH:MMmidnight,noon,weekdays- Standard cron syntax (passed through unchanged)
Eval Expressions
Evaluate mathematical and logical expressions safely (without using JavaScript's eval). Uses the subscript library for safe expression evaluation.
# Math operations
sum: ${eval(10 + 5)} # 15
multiply: ${eval(10 * 3)} # 30
divide: ${eval(100 / 4)} # 25
modulo: ${eval(17 % 5)} # 2
# Comparisons (returns boolean)
isGreater: ${eval(200 > 100)} # true
isLess: ${eval(100 > 200)} # false
isEqual: ${eval(10 == 10)} # true
# String comparisons
isEqual: ${eval("hello" == "hello")} # true
strictEqual: ${eval("foo" === "foo")} # true
notEqual: ${eval("a" != "b")} # true
# Complex expressions
complex: ${eval((10 + 5) * 2)} # 30
percentage: ${eval((75 / 100) * 200)} # 150
# With variables
threshold: 50
value: 75
aboveThreshold: ${eval(${value} > ${threshold})} # trueSupported operators:
| Category | Operators |
|----------|-----------|
| Arithmetic | + - * / % |
| Comparison | == != === !== > < >= <= |
| Logical | && \|\| ! |
| Grouping | ( ) |
Security:
- Does NOT use JavaScript's
eval() - Uses safe expression parser (subscript)
- No access to global scope or functions
- Only mathematical and logical operations allowed
If Expressions
Conditional expressions using ternary syntax. This is an alias for eval with a more intuitive name for conditionals.
# Basic ternary (condition ? "yes" : "no")
status: ${if(5 > 3 ? "yes" : "no")} # "yes"
# With variables
threshold: 50
value: 75
result: ${if(${value} > ${threshold} ? "above" : "below")} # "above"
# Nested ternary (if/else if/else)
score: 85
grade: ${if(${score} >= 90 ? "A" : ${score} >= 80 ? "B" : "C")} # "B"
# Boolean result (no ternary needed)
isValid: ${if(${value} > 0)} # true
# Logical operators
enabled: true
count: 5
canProceed: ${if(${enabled} && ${count} > 0)} # true
hasIssues: ${if(!${enabled} || ${count} == 0)} # falseSupported operators:
| Category | Operators |
|----------|-----------|
| Comparison | == != === !== > < >= <= |
| Logical | && \|\| ! |
| Nullish | ?? |
| Ternary | condition ? "yes" : "no" |
Serverless deployment examples:
service: my-service
provider:
name: aws
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'us-east-1'}
custom:
# Different memory by stage
memorySize: ${if(${provider.stage} === "prod" ? 1024 : 512)}
# Different log retention by stage
logRetention: ${if(${provider.stage} === "prod" ? 30 : 7)}
# Enable features per environment
enableDebugEndpoints: ${if(${provider.stage} !== "prod")}
enableMetrics: ${if(${provider.stage} === "prod")}
# Regional settings
replicaCount: ${if(${provider.region} === "us-east-1" ? 3 : 1)}
# Conditional IAM role (use predefined role in prod, inline in dev)
useExternalRole: ${if(${provider.stage} === "prod")}
role: ${if(${custom.useExternalRole} ? "arn:aws:iam::123:role/prod-role" : null)}
functions:
api:
handler: handler.api
memorySize: ${custom.memorySize}
# Debug function - only deployed in non-prod
debug:
handler: handler.debug
enabled: ${custom.enableDebugEndpoints}
# Metrics processor - only in prod
metricsProcessor:
handler: handler.metrics
enabled: ${custom.enableMetrics}Filters (Experimental)
Pipe resolved values through transformation functions like case conversion.
# String transformations
toUpperCaseString: ${'value' | toUpperCase } # 'VALUE'
toLowerCaseString: ${'VALUE' | toLowerCase } # 'value'
# Case conversions
toKebabCaseString: ${'valueHere' | toKebabCase } # 'value-here'
toCamelCaseString: ${'value-here' | toCamelCase } # 'valueHere'
# Chaining filters
key: lol_hi
transformed: ${key | toKebabCase | toUpperCase } # 'LOL-HI'
# With variables
serviceName: MyServiceName
serviceSlug: ${serviceName | toKebabCase} # 'my-service-name'Built-in filters:
toUpperCase- Convert to uppercasetoLowerCase- Convert to lowercasetoKebabCase- Convert to kebab-casetoCamelCase- Convert to camelCase
Custom filters:
const config = await configorama('config.yml', {
filters: {
// Custom filter
reverse: (value) => value.split('').reverse().join(''),
// Filter with options
truncate: (value, length = 10) => value.substring(0, length)
}
})# Using custom filters
reversed: ${'hello' | reverse} # 'olleh'
truncated: ${'very long string' | truncate(5)} # 'very 'Functions (Experimental)
Apply built-in functions to combine, transform, or manipulate values.
object:
one: once
two: twice
objectTwo:
three: third
four: fourth
# Merge objects
mergeObjects: ${merge(${object}, ${objectTwo})}
# Result: { one: 'once', two: 'twice', three: 'third', four: 'fourth' }
# String concatenation
fullName: ${concat(${firstName}, ' ', ${lastName})}
# Array operations
items:
- a
- b
- c
joinedItems: ${join(${items}, ', ')} # 'a, b, c'Built-in functions:
merge(obj1, obj2, ...)- Merge multiple objectsconcat(str1, str2, ...)- Concatenate stringsjoin(array, separator)- Join array elements
Custom functions:
const config = await configorama('config.yml', {
functions: {
// Custom function
add: (a, b) => a + b,
// Function with multiple args
between: (val, min, max) => val >= min && val <= max
}
})# Using custom functions
sum: ${add(5, 10)} # 15
value: 75
inRange: ${between(${value}, 50, 100)} # trueAPI Reference
Async API
The primary async API for resolving configurations.
Signature:
function configorama<T = any>(
configPathOrObject: string | object,
settings?: ConfigoramaSettings
): Promise<T | ConfigoramaResult<T>>Parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| configPathOrObject | string \| object | Yes | Path to config file or raw JavaScript object |
| settings | ConfigoramaSettings | No | Configuration options |
Settings object:
interface ConfigoramaSettings {
options?: Record<string, any> // CLI flags for ${opt:xyz}
syntax?: string | RegExp // Custom variable syntax
configDir?: string // Working directory for relative paths
variableSources?: VariableSource[] // Custom variable resolvers
filters?: Record<string, Function> // Custom filter functions
functions?: Record<string, Function> // Custom functions
allowUnknownVariableTypes?: boolean | string[] // Allow unknown var types
allowUnresolvedVariables?: boolean | string[] // Allow unresolved vars
allowUndefinedValues?: boolean // Allow undefined in output
returnMetadata?: boolean // Return metadata with config
mergeKeys?: string[] // Keys to merge in arrays
filePathOverrides?: Record<string, string> // Override file paths
}Returns:
- If
returnMetadata: false(default):Promise<T>- Resolved config object - If
returnMetadata: true:Promise<ConfigoramaResult<T>>- Object with config and metadata
Example:
const configorama = require('configorama')
// Basic usage
const config = await configorama('./config.yml')
// With options
const config = await configorama('./config.yml', {
options: { stage: 'prod', region: 'us-east-1' },
allowUnknownVariableTypes: ['ssm', 'cf']
})
// With metadata
const result = await configorama('./config.yml', {
returnMetadata: true,
options: { stage: 'prod' }
})
console.log(result.config) // Resolved config
console.log(result.metadata) // Variable metadata
console.log(result.resolutionHistory) // Resolution stepsSync API
Synchronous API for blocking config resolution.
Signature:
function configorama.sync<T = any>(
configPathOrObject: string | object,
settings?: ConfigoramaSettings
): TParameters:
Same as async API, but dynamicArgs cannot be a function (must be serializable).
Returns:
T - Resolved config object (synchronously)
Limitations:
- Cannot use async functions in JavaScript/TypeScript file references
dynamicArgsmust be serializable (not a function)- CLI args automatically parsed from
process.argvifoptionsnot provided
Example:
const configorama = require('configorama')
// Basic sync usage
const config = configorama.sync('./config.yml')
// With options
const config = configorama.sync('./config.yml', {
options: { stage: 'dev' }
})Analyze API
Analyze config structure without resolving variables.
Signature:
function configorama.analyze(
configPathOrObject: string | object,
settings?: ConfigoramaSettings
): Promise<AnalyzeResult>Returns:
interface AnalyzeResult {
originalConfig: object // Raw config object
variables: Variable[] // All variables found
uniqueVariables: Record<string, Variable[]> // Variables grouped by name
fileDependencies: string[] // File references
}
interface Variable {
variable: string // Full variable syntax (e.g., '${env:KEY}')
variableType: string // Type (e.g., 'env', 'opt', 'file')
variableName: string // Name/path (e.g., 'KEY')
variablePath: string // Location in config (e.g., 'database.host')
defaultValue: any // Default value if provided
hasDefault: boolean // Whether default exists
}Example:
const configorama = require('configorama')
const analysis = await configorama.analyze('./config.yml')
console.log(`Found ${analysis.variables.length} variables`)
console.log(`File dependencies:`, analysis.fileDependencies)
// List all environment variables required
const envVars = analysis.variables
.filter(v => v.variableType === 'env' && !v.hasDefault)
.map(v => v.variableName)
console.log('Required env vars:', envVars)Use cases:
- Generate documentation of required environment variables
- Validate config structure in CI/CD
- Build dependency graphs
- Audit external dependencies before resolution
Format Utilities
Parse various config formats to JavaScript objects.
Available parsers:
const { format } = require('configorama')
// Parse YAML
const yamlObj = format.yaml.parse('key: value')
// Parse JSON5
const jsonObj = format.json5.parse('{ key: "value", }')
// Parse TOML
const tomlObj = format.toml.parse('key = "value"')
// Parse INI
const iniObj = format.ini.parse('[section]\nkey=value')
// Parse HCL (requires @cdktf/hcl2json)
const hclObj = await format.hcl.parse('variable "example" { default = "value" }')Parser methods:
Each parser has:
parse(content)- Parse string to JavaScript objectstringify(obj)- Convert JavaScript object to format string (if supported)
Configuration Options
Custom Variable Syntax
Use the syntax option to change the variable delimiters. You can provide a regex string directly or use buildVariableSyntax() to generate one with proper character escaping:
const configorama = require('configorama')
const { buildVariableSyntax } = require('configorama')
// Using buildVariableSyntax helper (recommended)
const config = await configorama(configFile, {
syntax: buildVariableSyntax('{{', '}}'), // Mustache-style: {{env:FOO}}
options: { stage: 'dev' }
})
// Other examples:
buildVariableSyntax('${{', '}}') // ${{env:FOO}}
buildVariableSyntax('#{', '}') // #{env:FOO}
buildVariableSyntax('[[', ']]') // [[env:FOO]]
buildVariableSyntax('<', '>') // <env:FOO>Function signature:
function buildVariableSyntax(
prefix: string = '${',
suffix: string = '}',
excludePatterns: string[] = ['AWS', 'stageVariables']
): stringThe buildVariableSyntax() function:
- Automatically excludes suffix characters from the allowed character class (prevents parsing issues)
- Supports nested variables by excluding
$and{from values - Third parameter
excludePatternsis an array of strings to exclude via negative lookahead
Example with custom syntax:
const config = await configorama('config.yml', {
syntax: buildVariableSyntax('{{', '}}')
})# config.yml with {{ }} syntax
apiKey: {{env:API_KEY}}
stage: {{opt:stage, 'dev'}}
database: {{file(./db.yml)}}allowUnknownVariableTypes
Controls what happens when encountering unregistered variable types (e.g., ${ssm:path} when ssm isn't a registered resolver).
Type: boolean | string[]
Default: false
Behavior:
// Allow ALL unknown types to pass through
const config = await configorama(configFile, {
allowUnknownVariableTypes: true,
options: { stage: 'dev' }
})
// Input: { key: '${ssm:/path/to/secret}' }
// Output: { key: '${ssm:/path/to/secret}' }
// Allow only SPECIFIC unknown types
const config = await configorama(configFile, {
allowUnknownVariableTypes: ['ssm', 'cf'], // only these pass through
options: { stage: 'dev' }
})
// ${ssm:path} and ${cf:stack.output} pass through
// ${custom:thing} throws an errorUse cases:
- Multi-stage resolution (local resolution, then cloud provider resolves remaining vars)
- Serverless Framework integration (let framework resolve SSM, CloudFormation refs)
- Gradual migration (allow unknown types during transition period)
allowUnresolvedVariables
Controls what happens when a known resolver can't find a value (missing env vars, missing files, etc.).
Type: boolean | string[]
Default: false
Behavior:
// Allow ALL unresolved variables to pass through
const config = await configorama(configFile, {
allowUnresolvedVariables: true,
options: { stage: 'dev' }
})
// Input: { key: '${env:MISSING_VAR}' }
// Output: { key: '${env:MISSING_VAR}' }
// Allow only SPECIFIC types to be unresolved
const config = await configorama(configFile, {
allowUnresolvedVariables: ['param', 'file'], // only these pass through
options: { stage: 'prod' }
})
// Input: { paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
// Output: { paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
// Mixed scenario
const config = await configorama(configFile, {
allowUnresolvedVariables: ['param', 'file'],
options: { stage: 'prod' }
})
// Input: {
// key: '${env:MISSING_VAR}',
// paramKey: '${param:x}',
// fileKey: '${file(missing.yml)}'
// }
// Output: Error thrown because ${env:MISSING_VAR} cannot resolve
// (param and file pass through, but env vars must resolve)Important notes:
- This option does NOT apply to
self:or dotProp variables (e.g.,${foo.bar.baz}) - Self-references are local config errors, not external dependencies
- Useful for multi-stage resolution pipelines
Use cases:
- Serverless Dashboard resolves params after local resolution
- Gradual migration with optional external dependencies
- Development mode where some services are unavailable
Complete Options Reference
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| options | object | {} | CLI options/flags to populate ${opt:xyz} variables |
| syntax | string \| RegExp | ${...} | Custom variable syntax regex pattern |
| configDir | string | directory of config file | Working directory for relative file paths |
| variableSources | VariableSource[] | [] | Custom variable sources (see below) |
| filters | Record<string, Function> | {} | Custom filter functions for pipe operator |
| functions | Record<string, Function> | {} | Custom functions for ${fn(...)} syntax |
| allowUnknownVariableTypes | boolean \| string[] | false | Allow unknown variable types to pass through |
| allowUnresolvedVariables | boolean \| string[] | false | Allow known types that can't resolve to pass through |
| allowUndefinedValues | boolean | false | Allow undefined as a valid end result |
| returnMetadata | boolean | false | Return both config and metadata about variables |
| mergeKeys | string[] | [] | Keys to merge in arrays of objects |
| filePathOverrides | Record<string, string> | {} | Map of file paths to override (for testing/mocking) |
Legacy options (deprecated):
| Legacy Option | New Equivalent |
|---------------|----------------|
| allowUnknownVars | allowUnknownVariableTypes |
| allowUnknownVariables | allowUnknownVariableTypes |
| allowUnknownParams | allowUnresolvedVariables: ['param'] |
| allowUnknownFileRefs | allowUnresolvedVariables: ['file'] |
Custom Variable Sources
Configorama allows you to bring your own variable sources.
Variable Source Types
The source property defines how the config wizard handles each variable type:
| Source | Description | Wizard Behavior | Examples |
|--------|-------------|-----------------|----------|
| 'user' | Values provided by user at runtime | Prompt user for value | env, opt |
| 'config' | Values from config files or self-references | Check existence, can create | self, file, text |
| 'remote' | Values from external services | Fetch, prompt if missing, can write back | ssm, vault, consul |
| 'readonly' | Computed or system-derived values | Display only, cannot modify | git, cron, eval |
Built-in variable sources and their types:
| Variable | Source Type | Description |
|----------|-------------|-------------|
| ${env:VAR} | user | Environment variables |
| ${opt:flag} | user | CLI option flags |
| ${param:key} | user | Parameter values |
| ${self:key} | config | Self references |
| ${file(path)} | config | File references |
| ${text(path)} | config | Raw text file references |
| ${git:branch} | readonly | Git repository data |
| ${cron(expr)} | readonly | Cron expression conversion |
| ${eval(expr)} | readonly | Math/logic evaluation |
| ${if(expr)} | readonly | Conditional expressions |
Creating a Custom Resolver
There are 2 ways to resolve variables from custom sources:
Use built-in JavaScript method for sync or async resolution.
Add your own variable syntax and resolver:
const configorama = require('configorama')
const config = await configorama('path/to/configFile', {
variableSources: [{
// Variable type name (used in metadata)
type: 'consul',
// Source type for config wizard behavior
source: 'remote',
// Prefix shown in syntax examples
prefix: 'consul',
// Example syntax for documentation
syntax: '${consul:path/to/key}',
// Description for help text
description: 'Resolves values from Consul KV store',
// Match variables ${consul:xyz}
match: RegExp(/^consul:/g),
// Custom variable source. Must return a promise
resolver: async (varToProcess, opts, currentObject) => {
// varToProcess = 'consul:path/to/key'
const consulPath = varToProcess.replace(/^consul:/, '')
// Make remote call to consul
const consulClient = require('consul')()
const result = await consulClient.kv.get(consulPath)
return result.Value
}
}]
})
console.log(config)This would match:
key: ${consul:path/to/my/key}Variable source interface:
interface VariableSource {
type: string // Type name (e.g., 'consul', 'ssm')
source: 'user' | 'config' | 'remote' | 'readonly'
prefix?: string // Prefix for examples (defaults to type)
syntax: string // Example syntax (e.g., '${consul:key}')
description?: string // Help text description
match: RegExp // Regex to match variables
resolver: ( // Resolution function
variable: string, // Variable string (e.g., 'consul:key')
options: object, // Options from configorama call
currentConfig: object // Current partially-resolved config
) => Promise<any>
collectMetadata?: () => any // Optional: collect custom metadata
metadataKey?: string // Optional: key for custom metadata
}Advanced example with AWS SSM:
const AWS = require('aws-sdk')
const ssm = new AWS.SSM()
const config = await configorama('config.yml', {
variableSources: [{
type: 'ssm',
source: 'remote',
syntax: '${ssm:/path/to/parameter}',
description: 'Resolves values from AWS Systems Manager Parameter Store',
match: /^ssm:/,
resolver: async (variable, options, currentConfig) => {
const paramPath = variable.replace(/^ssm:/, '')
try {
const result = await ssm.getParameter({
Name: paramPath,
WithDecryption: true
}).promise()
return result.Parameter.Value
} catch (err) {
if (options.allowUnresolvedVariables) {
return `\${${variable}}` // Pass through unresolved
}
throw new Error(`SSM parameter not found: ${paramPath}`)
}
}
}]
})# config.yml
database:
password: ${ssm:/myapp/prod/db-password}
apiKey: ${ssm:/myapp/prod/api-key}CLI Usage
Configorama includes a CLI tool for resolving configs from the command line.
Basic Commands
# Resolve a config file
configorama config.yml
# Resolve and write to output file
configorama config.yml --output resolved.json
# Resolve with CLI options
configorama config.yml --stage prod --region us-east-1
# Show info about variables
configorama config.yml --info
# Verify config (check for errors without resolving)
configorama config.yml --verify
# Extract specific path from config
configorama config.yml database.host
# Output as YAML
configorama config.yml --format yamlCommand Options
Usage:
configorama [options] <file> [path]
Options:
-h, --help Show this help message
-v, --version Show version number
-o, --output <file> Write output to file instead of stdout
-f, --format <format> Output format: json, yaml, or js (default: json)
-d, --debug Enable debug mode
-i, --info Show info about the config
-V, --verify Verify the config
--param <key=value> Pass parameter values (can be used multiple times)
--allow-unknown Allow unknown variables to pass through
--allow-undefined Allow undefined values in the final output
Path Extraction:
configorama config.yml database.host Extract specific value
configorama config.yml functions[0] Extract from arrayCLI Examples
Basic resolution:
# Input: config.yml
apiKey: ${env:API_KEY}
stage: ${opt:stage, 'dev'}
# Command
export API_KEY=secret123
configorama config.yml --stage prod
# Output
{
"apiKey": "secret123",
"stage": "prod"
}With parameters:
configorama config.yml \
--stage prod \
--param "domain=myapp.com" \
--param "apiKey=secret123"Extract specific path:
# config.yml
database:
host: localhost
port: 5432
# Extract database.host
configorama config.yml database.host
# Output: localhost
# Extract database config as JSON
configorama config.yml database --format json
# Output: {"host":"localhost","port":5432}Output to file:
configorama config.yml --output resolved.json
configorama config.yml --output resolved.yml --format yamlShow variable info:
configorama config.yml --info
# Output:
# Found 15 variables
# env: 5
# opt: 3
# self: 4
# file: 2
# git: 1
#
# Required environment variables:
# - API_KEY
# - DB_HOST
# - DB_PASSWORD
#
# File dependencies:
# - ./secrets.yml
# - ./config/database.ymlVerify without resolving:
configorama config.yml --verify
# Output:
# ✓ Config structure valid
# ✓ No circular dependencies
# ✓ All file references exist
# ! Warning: 3 environment variables not set
# - API_KEY
# - DB_HOST
# - DB_PASSWORDTesting
Running Tests
# Run all tests
npm test
# Run only fast tests (excludes slow tests)
npm run test:lib
# Run API tests
npm run test:api
# Run tests in a specific directory
npm run test:tests
# Run slow tests only
npm run test:slow
# Watch mode (reruns on file changes)
npm run watch
# Type checking
npm run typecheckTest Structure
tests/
├── _fixtures/ # Shared test fixtures
├── api/ # API tests
├── asyncValues/ # Async function resolution tests
├── syncValues/ # Sync function resolution tests
├── cronValues/ # Cron expression tests
├── gitVariables/ # Git variable tests
├── filePathOverrides/ # File path override tests
├── filterTests/ # Filter tests
├── hclTests/ # Terraform HCL tests
├── iniTests/ # INI format tests
├── tomlTests/ # TOML format tests
├── jsTests/ # JavaScript file tests
└── ... # More test categoriesWriting Tests
Configorama uses the uvu test framework. Tests can be run directly with Node.js:
# Run a single test file
node tests/api/api.test.jsExample test:
const { test } = require('uvu')
const assert = require('uvu/assert')
const path = require('path')
const configorama = require('../src')
test('resolves environment variables', async () => {
process.env.TEST_VAR = 'test-value'
const config = await configorama({
key: '${env:TEST_VAR}'
})
assert.equal(config.key, 'test-value')
delete process.env.TEST_VAR
})
test('handles missing env vars with defaults', async () => {
const config = await configorama({
key: '${env:MISSING_VAR, "default"}'
})
assert.equal(config.key, 'default')
})
test.run()Test utilities available at tests/utils.js:
const { getFixturePath, loadFixture } = require('./tests/utils')
// Get path to fixture file
const fixturePath = getFixturePath('config.yml')
// Load and parse fixture
const fixtureData = loadFixture('config.yml')Deployment
Using with Serverless Framework
Configorama can be used as a drop-in replacement for the Serverless Framework variable system.
serverless.js:
const path = require('path')
const configorama = require('configorama')
const args = require('minimist')(process.argv.slice(2))
// Path to serverless config to be parsed
const yamlFile = path.join(__dirname, 'serverless.config.yml')
module.exports = configorama.sync(yamlFile, { options: args })serverless.config.yml:
service: my-service
provider:
name: aws
runtime: nodejs18.x
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'us-east-1'}
# Environment-specific config
environment:
STAGE: ${opt:stage}
DB_HOST: ${env:DB_HOST}
API_KEY: ${ssm:/my-service/${opt:stage}/api-key}
custom:
# Load stage-specific config
stageConfig: ${file(./config/${opt:stage}.yml)}
# Git info for tracking
deploymentInfo:
branch: ${git:branch}
commit: ${git:sha1}
timestamp: ${timestamp}
functions:
api:
handler: handler.api
memorySize: ${if(${provider.stage} === 'prod' ? 1024 : 512)}
events:
- http:
path: /
method: ANYDeploy:
# Deploy to dev
serverless deploy --stage dev
# Deploy to production
serverless deploy --stage prod --region us-west-2Docker Deployment
Dockerfile:
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Set environment variables (can be overridden at runtime)
ENV NODE_ENV=production
ENV STAGE=prod
# Run config resolution at build time (optional)
# RUN node -e "require('configorama').sync('./config.yml', { options: { stage: process.env.STAGE } })"
CMD ["node", "index.js"]docker-compose.yml:
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=production
- STAGE=prod
- DB_HOST=postgres
- DB_PASSWORD=${DB_PASSWORD}
- API_KEY=${API_KEY}
depends_on:
- postgres
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}Usage:
# Build
docker build -t myapp .
# Run with environment variables
docker run \
-e DB_HOST=mydb.example.com \
-e DB_PASSWORD=secret \
-e API_KEY=abc123 \
myappCI/CD Integration
GitHub Actions example (.github/workflows/deploy.yml):
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Verify config
run: npx configorama config.yml --verify
env:
STAGE: prod
DB_HOST: ${{ secrets.DB_HOST }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
API_KEY: ${{ secrets.API_KEY }}
- name: Run tests
run: npm test
- name: Deploy to production
run: npm run deploy
env:
STAGE: prod
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}GitLab CI example (.gitlab-ci.yml):
stages:
- verify
- test
- deploy
verify-config:
stage: verify
image: node:18
script:
- npm ci
- npx configorama config.yml --verify --stage $CI_ENVIRONMENT_NAME
variables:
STAGE: $CI_ENVIRONMENT_NAME
test:
stage: test
image: node:18
script:
- npm ci
- npm test
deploy-production:
stage: deploy
image: node:18
script:
- npm ci
- npm run deploy
environment:
name: production
only:
- main
variables:
STAGE: prodTroubleshooting
Common Issues
Q: Variable not resolving
# Problem
apiKey: ${env:API_KEY}
# Error: Environment variable 'API_KEY' not foundSolutions:
- Ensure environment variable is set:
echo $API_KEY - Check variable name spelling
- Use default value:
${env:API_KEY, 'default'} - Allow unresolved vars:
allowUnresolvedVariables: ['env']
Q: Circular dependency error
# Problem
a: ${self:b}
b: ${self:a}
# Error: Circular variable dependency detected: b → a → bSolutions:
- Restructure config to remove circular reference
- Use intermediate values to break the cycle
- Consider if one value should be a constant instead
Q: File not found
# Problem
secrets: ${file(./secrets.yml)}
# Error: File not found: ./secrets.ymlSolutions:
- Check file path is correct relative to config file
- Ensure file exists:
ls -la secrets.yml - Use absolute path:
${file(/absolute/path/to/secrets.yml)} - Add default:
${file(./secrets.yml), {}}
Q: TypeScript file execution fails
# Problem
config: ${file(./config.ts)}
# Error: Cannot find module 'tsx'Solutions:
- Install tsx:
npm install tsx --save-dev - Or install ts-node:
npm install ts-node typescript --save-dev - Ensure TypeScript file exports correctly:
export = valueormodule.exports = value
Q: HCL parsing fails
# Problem
terraform: ${file(./main.tf)}
# Error: HCL parsing requires @cdktf/hcl2jsonSolutions:
- Install dependency:
npm install @cdktf/hcl2json - Ensure HCL file is valid Terraform syntax
- Check file extension is
.tf,.hcl, or.tf.json
Debug Mode
Enable debug mode to see detailed resolution steps:
CLI:
configorama config.yml --debugProgrammatic:
const config = await configorama('config.yml', {
returnMetadata: true
})
// Inspect resolution history
console.log(config.resolutionHistory)Environment variable:
DEBUG=configorama:* node app.jsOutput example:
configorama:resolve Resolving variable: ${env:API_KEY}
configorama:resolve Type: env, Name: API_KEY
configorama:resolve Resolved to: secret-key-123
configorama:resolve Resolving variable: ${opt:stage}
configorama:resolve Type: opt, Name: stage
configorama:resolve Resolved to: prodCircular Dependencies
Configorama detects circular dependencies and provides helpful error messages:
# Direct cycle
a: ${self:b}
b: ${self:a}Error:
Circular variable dependency detected: b → a → b
Resolution path:
1. Started resolving 'b'
2. Required 'a' (from ${self:b})
3. Required 'b' (from ${self:a})
4. Circular dependency detected
To fix this, restructure your config to break the circular reference.How to fix:
- Use intermediate values:
# Before (circular)
a: ${self:b}
b: ${self:a}
# After (fixed)
base: value
a: ${self:base}
b: ${self:base}- Make one value a constant:
# Before (circular)
apiUrl: ${self:baseUrl}/api
baseUrl: ${self:apiUrl}/v1
# After (fixed)
baseUrl: https://example.com
apiUrl: ${self:baseUrl}/api/v1- Restructure dependencies:
# Before (circular)
database:
connectionString: postgres://${database.host}:${database.port}/${database.name}
host: ${self:database.connectionString}
# After (fixed)
database:
host: localhost
port: 5432
name: mydb
connectionString: postgres://${database.host}:${database.port}/${database.name}FAQ
Q: What happens with circular variable dependencies?
Configorama detects circular dependencies and throws a helpful error instead of hanging forever. See Circular Dependencies section for examples and fixes.
Q: Why should I use this?
Never render a stale configuration file again! Configorama ensures your configs are always up-to-date with the latest environment variables, CLI flags, file contents, and custom sources.
Q: Does this work with serverless.yml?
Yes! Use serverless.js as your main entry point. See Using with Serverless Framework for full example.
Q: Can I use this with other frameworks/tools?
Yes! Configorama is framework-agnostic. It works with any tool that accepts a JavaScript object or can import a .js file. Examples:
- Webpack:
webpack.config.js - Vite:
vite.config.js - Jest:
jest.config.js - ESLint:
eslint.config.js - Docker Compose: Generate yaml from resolved config
- Kubernetes: Generate manifests from resolved config
Q: How do I handle secrets securely?
Best practices:
- Use environment variables:
apiKey: ${env:API_KEY}- Fetch from secret managers:
secrets: ${file(./fetch-secrets.js)}// fetch-secrets.js
const AWS = require('aws-sdk')
const ssm = new AWS.SSM()
module.exports = async () => {
const result = await ssm.getParameter({
Name: '/myapp/api-key',
WithDecryption: true
}).promise()
return result.Parameter.Value
}- Never commit secrets to version control
- Use
.gitignorefor secret files - Rotate secrets regularly
Q: Can I use variables in variable syntax?
Yes! Variables are resolved recursively:
stage: prod
configFile: config-${stage}.yml
config: ${file(${configFile})}
# Resolves: ${file(config-prod.yml)}Q: How do I migrate from Serverless Framework variables?
Configorama is mostly compatible with Serverless Framework variable syntax. Key differences:
- Cleaner self-references:
# Serverless
key: ${self:other.key}
# Configorama (both work)
key: ${self:other.key}
key: ${other.key}- Numbers as defaults:
# Configorama supports numeric defaults
timeout: ${env:TIMEOUT, 30}- Additional variable types:
${cron()}- Cron expressions${eval()}- Math expressions${if()}- Conditionals${git:}- Git data
Advanced Usage
Multi-Stage Resolution
Resolve configs in multiple stages, allowing external systems to handle remaining variables:
// Stage 1: Local resolution (resolve env, opt, file, etc.)
const partiallyResolve