Keep your
.env.examplein sync with.env— before a missing variable breaks someone's build.
Every team has hit this: you add STRIPE_SECRET_KEY to your local .env, ship the feature, and forget to add it to .env.example. A teammate pulls, runs the app, and gets a cryptic crash three layers deep. Or the reverse — .env.example lists a variable nobody uses anymore.
envsync is a tiny, zero-dependency CLI that keeps the two files honest. It works with any language or framework — anything that uses a .env file. Run it locally, in a pre-commit hook, or as a CI gate.
$ npx envsync
envsync — checking .env against .env.example
✖ 1 key in .env missing from .env.example:
+ STRIPE_SECRET_KEY
Run envsync gen to update .env.example.
✖ Drift detected (1 issue).$ npx envsync gen
✔ Wrote .env.example (+1)
+ STRIPE_SECRET_KEY- Zero dependencies. One small package, nothing transitive. Fast to install, nothing to audit.
- Language-agnostic. Node, Python, Ruby, Go, Rust, PHP — if it reads a
.env, this works. - CI-friendly. Clear exit codes and a
--jsonmode. Drop it into any pipeline. - Never leaks secrets.
gencopies keys, never values. Your.env.examplestays safe to commit. - Non-destructive.
genpreserves the comments and placeholder values you already curated.
Run it directly from GitHub — no install, always the latest:
npx github:nandukmelath/envsyncnpm release coming soon. Once published, the shorter forms below will work; until then use the
github:form above.
npx envsync # run once
npm install --save-dev envsync # add to a project
npm install --global envsync # install globallyCompare .env against .env.example and exit non-zero if their key sets differ:
envsync # same as: envsync checkCreate .env.example from .env, or add any keys it's missing — values blanked out:
envsync gengen is non-destructive: it keeps every comment and placeholder already in your .env.example and only appends what's missing. Use --prune to also remove keys that no longer exist in .env, or --force to rebuild from scratch.
envsync [check] [options] Compare .env against .env.example (default)
envsync gen [options] Create/update .env.example from .env
envsync --help | --version
| Option | Description |
|---|---|
--env <path> |
Path to the env file (default .env) |
--example <path> |
Path to the example file (default .env.example) |
--ignore <a,b,c> |
Comma-separated keys to exclude from the comparison |
--check-values |
Also fail when a key in .env has an empty value |
--allow-missing-in-env |
Don't fail when .env.example has keys absent from .env (e.g. optional vars) |
--json |
Machine-readable output |
--quiet |
Suppress the header line |
--no-color |
Disable ANSI colours |
| Option | Description |
|---|---|
--env <path> |
Source env file (default .env) |
--example <path> |
Target example file (default .env.example) |
--placeholder=<text> |
Value to write for each key (default: empty) |
--prune |
Remove keys no longer present in .env |
--force |
Rebuild the example from .env, discarding the existing layout |
--dry-run |
Report what would change; write nothing (exit 1 if stale) |
--json |
Machine-readable output |
| Code | Meaning |
|---|---|
0 |
In sync / up to date |
1 |
Drift detected |
2 |
Usage or IO error (e.g. .env not found) |
The check is most valuable where a real .env exists — so the best place to run it is a pre-commit hook, with CI as a backstop.
Catch drift before it's ever committed — the .env on your machine is the source of truth:
# .git/hooks/pre-commit (remember: chmod +x)
#!/bin/sh
npx envsync || {
echo "envsync: .env and .env.example are out of sync. Run 'npx envsync gen'."
exit 1
}With Husky:
npx husky add .husky/pre-commit "npx envsync"CI usually has no committed .env (it's secret). The honest, useful pattern is to verify the .env your pipeline already builds from secrets is fully documented in the committed .env.example:
# .github/workflows/env-check.yml
name: env-check
on: [push, pull_request]
jobs:
envsync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
# Your CI probably already does something like this to run the app/tests.
# `--allow-missing-in-env` ignores optional keys that this job doesn't set.
- run: |
printf 'DATABASE_URL=%s\n' "${{ secrets.DATABASE_URL }}" >> .env
printf 'STRIPE_SECRET_KEY=%s\n' "${{ secrets.STRIPE_SECRET_KEY }}" >> .env
- run: npx envsync --allow-missing-in-envThis fails the build the moment a developer adds a variable to the running config but forgets to document it in .env.example.
The core is exported and pure — no filesystem, no process.exit:
const { compare, generate, parse } = require('envsync');
const result = compare(envText, exampleText);
// → { missingInExample, missingInEnv, emptyInEnv, matched, env, example }
const { content, added, removed } = generate(envText, exampleText, { prune: true });A line is treated as a variable when it looks like KEY=value, with these niceties:
export KEY=value(shell-style env files) — theexportprefix is recognised.- Quoted values:
KEY="value"andKEY='value'. - Values that contain
=, likeDATABASE_URL=postgres://u:p@h/db?ssl=true. # commentsand blank lines are preserved when generating.- Empty values (
KEY=,KEY="") are detected by--check-values.
Keys must match ^[A-Za-z_][A-Za-z0-9_]*$. Anything else is left untouched.
A note on secrets:
genonly ever copies key names and the comments you wrote — never values. If you keep secret material inside comments in your.env, those comments would be copied; keep comments descriptive, not sensitive.
How is this different from dotenv?
dotenv loads variables at runtime. envsync audits the files at dev/CI time. They're complementary.
Does it run my code or read my secrets?
No. It parses text files locally and prints a report. Values are never transmitted anywhere, and gen never writes them out.
Multiple env files (.env, .env.local)?
Point --env / --example at the pair you want. Run it once per pair (e.g. in an npm script). First-class multi-file support is on the roadmap.
- Multiple env/example pairs in one invocation
- Config block in
package.json - Optional value-format rules (URL, number, non-empty) for
--check-values - Published npm release + provenance
Issues and PRs welcome. The whole tool is two small files (src/index.js, bin/envsync.js) with a full test suite:
git clone https://github.com/nandukmelath/envsync
cd envsync
npm testMIT © Nandu Kannan Melath