Compare .env files across environments, branches, and runtimes. Catch missing variables, type mismatches, and configuration drift before deployment.
Built by AdametherzLab with Claude.
Environment variable mismatches are one of the most common causes of deployment failures. A missing DATABASE_URL, a PORT that changed from number to string, a secret that exists in development but not production — these bugs are silent until they break production.
env-diff catches them all.
- Type-aware diffing — Catches when
PORT=3000(number) becomesPORT="3000"(string) - Git branch comparison —
env-diff .env:main .env:stagingcompares across branches - Secret masking — Automatically detects and masks API keys, tokens, and passwords
- Multi-file matrix — Compare dev, staging, and prod simultaneously
- Codebase scanning — Auto-generate
.env.examplefrom your source code - Sync mode — Generate patches to fix environment drift
- Watch mode — Live drift detection during development
- CI/CD ready — GitHub Action, strict mode, JSON output for automation
- MCP server — Native integration for AI coding agents (Claude Code, Cursor)
- Zero dependencies — Pure TypeScript, runs on Bun or Node.js 20+
- Config file — Persistent settings via
.envdiffrc.json
| Feature | env-diff | dotenv-linter | dotenv-diff | dotenvx |
|---|---|---|---|---|
| Type-aware diffing | yes | no | no | no |
| Git branch comparison | yes | no | no | no |
| Secret masking | yes | no | no | yes |
| Multi-file matrix | yes | no | no | no |
| Codebase scanning | yes | no | no | no |
| MCP server (AI agents) | yes | no | no | no |
| GitHub Action | yes | no | no | no |
| Watch mode | yes | no | no | no |
| Sync mode | yes | no | no | no |
| Zero dependencies | yes | yes | no | no |
| Config file | yes | yes | no | yes |
| CI/CD strict mode | yes | yes | no | yes |
npm install -g @adametherzlab/env-diff
# or
bun add -g @adametherzlab/env-diff
# or use directly
npx @adametherzlab/env-diff .env.example .env.productionenv-diff .env.example .env.productionenv-diff .env:main .env:staging
env-diff .env@abc123 .env@HEADenv-diff .env.example .env.production --strict
# Exits with code 1 if errors detectedenv-diff <left> <right> [options]
Options:
--strict Exit with code 1 if errors detected
--ignore <key> Ignore specific keys (repeatable)
--no-value-diff Only check key existence and types
--format <type> Output: table (default), json, markdown, summary
--mask Mask detected secret values
--watch Re-diff on file changes
--sync Show patch to fix missing keys
--sync-write Apply sync patch to target file
Subcommands:
--scan [dir] Scan codebase for env var references
--scan-write [dir] Generate .env.example from scan
--matrix <files> Compare multiple files simultaneously
--install-hook Install pre-commit git hook
+──────────────+──────────────+──────────────+───────────────+
│ Key │ .env.example │ .env.prod │ Status │
+──────────────+──────────────+──────────────+───────────────+
│ DATABASE_URL │ postgres://… │ (undefined) │ removed │
│ PORT │ 3000 │ "3000" │ type-mismatch │
│ API_KEY │ (undefined) │ sk-l**** │ added │
+──────────────+──────────────+──────────────+───────────────+
✖ Environment check failed with errors
env-diff .env.example .env.prod --format jsonenv-diff .env.example .env.prod --format markdownimport { parseEnvFile, diffEnvMaps, parseProcessEnv } from "@adametherzlab/env-diff";
// Compare .env files
const template = parseEnvFile(".env.example");
const runtime = parseProcessEnv();
const result = diffEnvMaps(template, runtime, "Template", "Runtime");
if (result.hasErrors) {
console.error("Missing required environment variables!");
result.entries
.filter(e => e.severity === "error")
.forEach(e => console.error(` - ${e.key}: ${e.status}`));
process.exit(1);
}Compare .env files across any git ref (branch, tag, commit):
# Compare main vs staging
env-diff .env:main .env:staging
# Compare current vs specific commit
env-diff .env@abc123 .env
# PR review: compare base branch vs HEAD
env-diff .env:origin/main .env:HEADenv-diff automatically detects sensitive keys and masks their values:
env-diff .env.example .env.production --mask
# API_KEY: sk-l**** | SECRET_TOKEN: rea****Default patterns: *KEY*, *SECRET*, *TOKEN*, *PASSWORD*, *CREDENTIAL*, *AUTH*, *PRIVATE*
Configure custom patterns in .envdiffrc.json:
{
"secretPatterns": ["*KEY*", "*SECRET*", "STRIPE_*", "AWS_*"]
}Auto-generate .env.example from your source code:
# Preview what variables your code uses
env-diff --scan ./src
# Generate .env.example
env-diff --scan-write ./srcOutput:
# Referenced in: src/db.ts, src/config.ts
DATABASE_URL=
# Referenced in: src/server.ts
PORT=3000Compare all environments simultaneously:
env-diff --matrix .env.dev .env.staging .env.prodFix environment drift automatically:
# Preview missing keys
env-diff .env.example .env.local --sync
# Apply patch
env-diff .env.example .env.local --sync-writeCreate .envdiffrc.json in your project root:
{
"ignoreKeys": ["NODE_ENV", "CI", "PATH"],
"caseSensitive": true,
"strict": true,
"format": "table",
"secretPatterns": ["*KEY*", "*SECRET*", "*TOKEN*"],
"compareValues": true
}Or add to package.json:
{
"envdiff": {
"ignoreKeys": ["NODE_ENV"],
"strict": true
}
}- uses: AdametherzLab/env-diff@v1
with:
left: '.env.example'
right: '.env.production'
strict: 'true'
ignore: 'NODE_ENV,CI'
mask-secrets: 'true'steps:
- run: npx @adametherzlab/env-diff .env.example .env.production --strict --maskenv-diff --install-hookenv-diff includes an MCP (Model Context Protocol) server for AI coding agents:
# Add to Claude Code, Cursor, or any MCP-compatible agent
env-diff-mcpAvailable tools:
env_diff_compare— Compare .env files or contentenv_diff_scan— Scan codebase for env referencesenv_diff_sync— Generate sync patches
Live drift detection during development:
env-diff .env.example .env --watch
# Re-diffs automatically when either file changes| Function | Description |
|---|---|
parseEnvString(content, options?) |
Parse raw .env content into typed EnvMap |
parseEnvFile(filePath, options?) |
Read and parse .env file |
parseProcessEnv() |
Wrap process.env into typed EnvMap |
diffEnvMaps(left, right, leftLabel, rightLabel, options?) |
Compare two EnvMaps |
loadGitEnv(source, options?) |
Load .env from git ref |
scanForEnvVars(directory, extensions?) |
Scan code for env references |
syncEnvFiles(source, target, options?) |
Generate sync patch |
compareMatrix(filePaths, options?) |
Multi-file comparison |
watchEnvFiles(left, right, options?) |
Live file watching |
isSecretKey(key, patterns?) |
Check if key is a secret |
maskValue(value) |
Mask a sensitive value |
formatTable(result) |
Format as colored table |
formatJson(result) |
Format as JSON |
formatMarkdown(result) |
Format as markdown table |
| Status | Severity | Meaning |
|---|---|---|
removed |
error | Key missing in target |
type-mismatch |
error | Same key, different types |
added |
warning | New key in target only |
modified |
warning | Same key & type, different value |
unchanged |
info | Identical |
See CHANGELOG.md for version history.
See CONTRIBUTING.md
MIT (c) AdametherzLab — Built with Claude