envdiff
Compare, validate, and sync .env files across environments.

Features
- Diff two .env files with added/removed/changed detection
- Compare N files side-by-side in a matrix view
- Check .env against .env.example for missing keys
- Validate .env against TOML schema (types, required, patterns, enums)
- Generate .env.example from existing .env
- Diff .env against its last git commit
- Interactive sync TUI to selectively apply changes
- Pre-commit hook to catch .env drift
- CI mode with GitHub Actions annotations
- JSON, table, and GitHub annotation output formats
- Secret masking with
--mask
- Key filtering with
--ignore glob patterns
Requirements
- macOS or Linux
- Go 1.25 or newer (for building from source)
Installation
Homebrew (macOS)
brew tap shahadulhaider/tap
brew install envdiff
go install
go install github.com/shahadulhaider/envdiff/cmd/envdiff@latest
Download binary
Pre-built binaries for macOS and Linux are available on the GitHub Releases page.
Build from source
git clone https://github.com/shahadulhaider/envdiff
cd envdiff
make build
./envdiff
Usage
# Diff two .env files
envdiff diff .env.dev .env.prod
# JSON output
envdiff diff .env.dev .env.prod --format json
# Mask secret values
envdiff diff .env.dev .env.prod --mask
# Check .env against .env.example
envdiff check
# Compare multiple files side-by-side
envdiff compare .env.dev .env.staging .env.prod
# Validate against schema
envdiff validate --schema .env.schema.toml .env
# Generate .env.example
envdiff init
# Diff against last git commit
envdiff git .env
# Interactive sync
envdiff sync source.env target.env
# Install pre-commit hook
envdiff hook install
# CI mode with GitHub Actions annotations
envdiff ci --require .env.example
Output Examples
Diff output is one line per key, prefixed by what changed:
| Prefix |
Meaning |
- |
Key is in the first file but not the second |
~ |
Key is in both files with a different value |
+ |
Key is in the second file but not the first |
Catch drift before it ships
check compares the key sets of .env and .env.example, ignoring values. It
prints nothing and exits 0 when they agree, so it works as a CI gate.

Every environment, side by side
compare lines up any number of files in a single matrix, truncating long
values and marking absent keys as <missing>.

Apply changes selectively
sync opens a TUI listing every pending change between two files, colour-coded
by kind and ticked by default. Navigate with the arrow keys or j/k, press
space to untick anything you want to skip, enter to apply, q to cancel
without writing. Comments and blank lines in the target file are preserved.

Commands
| Command |
Description |
diff |
Compare two .env files |
check |
Check .env against .env.example |
compare |
Compare N files side-by-side |
validate |
Validate .env against TOML schema |
init |
Generate .env.example from .env |
git |
Diff .env against last git commit |
sync |
Interactive TUI to apply changes |
hook |
Install/uninstall pre-commit hook |
ci |
CI mode with annotations |
Global Flags
| Flag |
Description |
--format |
Output format: table, json, github (default: table) |
--mask |
Hide secret values in output |
--ignore |
Glob pattern for keys to skip |
--no-values |
Show keys only, no values |
--color |
Color mode: auto, always, never |
Exit Codes
| Code |
Meaning |
0 |
No differences / validation passed |
1 |
Differences found / validation failed |
2 |
Error (file not found, parse error) |
envdiff uses TOML for schema validation:
allow_extra = true
[vars.DB_HOST]
required = true
type = "string"
[vars.DB_PORT]
required = true
type = "number"
default = "5432"
[vars.LOG_LEVEL]
required = false
type = "enum"
enum = ["debug", "info", "warn", "error"]
[vars.API_KEY]
required = true
type = "string"
pattern = "^[A-Za-z0-9]{32,}$"
Supported types: string, number, bool, url, email, enum
License
GNU General Public License v3.0 — see LICENSE for details.