Part of Tuulbelt — A collection of zero-dependency tools.
Merge configuration from ENV variables, config files, and CLI arguments with clear precedence rules and source tracking.
Applications often need configuration from multiple sources:
- Environment variables (
DATABASE_URL=...) - Config files (JSON)
- CLI arguments (
--port 3000)
Existing solutions require heavy dependencies (dotenv, convict, cosmiconfig), don't handle all sources, or have unclear precedence rules.
cfgmerge solves this with zero dependencies and explicit precedence.
- Zero runtime dependencies — standard library only
- Clear precedence — CLI > ENV > File > Defaults
- Source tracking — know where each value came from
- Type coercion — automatic parsing of numbers, booleans, null
- Prefix filtering — filter ENV vars by prefix (e.g.,
APP_) - Composable — use as CLI or library
git clone https://github.com/tuulbelt/config-file-merger.git
cd config-file-merger
npm install # Install dev dependencies onlyCLI names — both short and long forms work:
- Short (recommended):
cfgmerge - Long:
config-file-merger
Recommended setup:
npm link # Enable the 'cfgmerge' command globally
cfgmerge --help# Merge env vars with APP_ prefix and a config file
cfgmerge --env --prefix APP_ --file config.json
# Override with CLI args
cfgmerge --file config.json --args "port=3000,debug=true"
# Track where each value came from
cfgmerge --file config.json --args "port=3000" --track-sources
# Use defaults with env overrides
cfgmerge --defaults defaults.json --env --prefix MY_APP_Output (without --track-sources):
{
"port": 3000,
"debug": true,
"host": "localhost"
}Output (with --track-sources):
{
"port": { "value": 3000, "source": "cli" },
"debug": { "value": true, "source": "cli" },
"host": { "value": "localhost", "source": "file" }
}import { mergeConfig, getValue, parseJsonFile } from './src/index.js';
// Load config file
const fileResult = parseJsonFile('config.json');
if (!fileResult.ok) {
console.error(fileResult.error);
process.exit(1);
}
// Merge from multiple sources
const result = mergeConfig({
defaults: { port: 8080, host: 'localhost' },
file: fileResult.data,
env: process.env,
envPrefix: 'APP_',
cli: { debug: true },
trackSources: false,
});
if (result.ok) {
const port = getValue<number>(result.config, 'port', 8080);
console.log(`Starting on port ${port}`);
}Values are merged in this order (highest precedence first):
- CLI arguments (
--args) — explicit overrides - Environment variables (
--env) — deployment config - Config file (
--file) — application defaults - Default values (
--defaults) — fallback values
| Option | Short | Description |
|---|---|---|
--env |
-e |
Include environment variables |
--prefix PREFIX |
-p |
Filter env vars by prefix (e.g., APP_) |
--no-strip-prefix |
Keep prefix in output keys | |
--no-lowercase |
Keep original env var case | |
--file FILE |
-f |
Load config from JSON file |
--defaults FILE |
-d |
Load default values from JSON file |
--args ARGS |
-a |
CLI arguments as key=value,key2=value2 |
--track-sources |
-t |
Show source of each value in output |
--help |
-h |
Show help message |
--version |
-V |
Show version |
Merge configuration from multiple sources.
interface MergeOptions {
env?: Record<string, string | undefined>; // Environment variables
envPrefix?: string; // Filter by prefix
stripPrefix?: boolean; // Remove prefix from keys (default: true)
lowercaseEnv?: boolean; // Lowercase env keys (default: true)
file?: Record<string, unknown>; // Config file contents
cli?: Record<string, unknown>; // CLI arguments
defaults?: Record<string, unknown>; // Default values
trackSources?: boolean; // Include source in output
}Parse a JSON config file.
const result = parseJsonFile('config.json');
if (result.ok) {
console.log(result.data); // { port: 3000, ... }
} else {
console.error(result.error);
}Parse CLI arguments in key=value,key2=value2 format.
const parsed = parseCliArgs('port=3000,debug=true');
// { port: { value: 3000, source: 'cli' }, debug: { value: true, source: 'cli' } }Parse environment variables with optional filtering.
const parsed = parseEnv(process.env, 'APP_');
// { port: { value: '3000', source: 'env' }, host: { value: 'localhost', source: 'env' } }Get a typed value from merged config.
const port = getValue<number>(config, 'port', 8080);
const debug = getValue<boolean>(config, 'debug', false);CLI arguments are automatically parsed:
| Input | Output | Type |
|---|---|---|
"true" |
true |
boolean |
"false" |
false |
boolean |
"null" |
null |
null |
"42" |
42 |
number |
"3.14" |
3.14 |
number |
"hello" |
"hello" |
string |
'"42"' |
"42" |
string (quoted) |
- Prototype pollution prevention: Built-in protection against
__proto__,constructor,prototypekeys - No schema validation: Zero-dependency principle—validate merged config structure in your application if needed
- Type coercion: CLI argument strings are automatically parsed to numbers, booleans, or null
- Environment filtering: Use
--prefixto include only expected environment variables
npm test # Run all tests
npm test -- --watch # Watch modeSee examples/ directory:
npx tsx examples/basic.ts
npx tsx examples/advanced.tsConfig File Merger uses other Tuulbelt tools to validate its reliability:
./scripts/dogfood-flaky.sh [runs]
# Validates all 135 tests are deterministic
# Default: 10 runs = 1,350 test executions./scripts/dogfood-diff.sh
# Proves config merging produces identical output for identical inputSee DOGFOODING_STRATEGY.md for composition details.
Exit codes:
0— Success1— Error (file not found, invalid JSON)
Errors are returned via Result pattern, not thrown:
const result = parseJsonFile('missing.json');
if (!result.ok) {
console.error(result.error); // "File not found: missing.json"
}See SPEC.md for detailed technical specification including:
- Precedence rules and merge behavior
- Type coercion rules
- Environment variable handling
- Error cases and exit codes
▶ View interactive recording on asciinema.org
MIT — see LICENSE
Part of the Tuulbelt collection:
- Test Flakiness Detector — Detect unreliable tests
- CLI Progress Reporting — Concurrent-safe progress updates
- Structured Error Handler — Error context preservation
