Env file diff checker

Paste a .env.example and your own .env to see which keys are missing, extra, defined twice, empty, or malformed. You also get a paste-ready block of the missing keys, with the example’s defaults or names only. The check compares key names and emptiness, never values, and it runs in your browser, so nothing you paste is sent anywhere.

Updated

Worked example

Example input
--- example ---
# Server
PORT=3000
NODE_ENV=development

# Database
DATABASE_URL=postgres://app:app@localhost:5432/app_dev
REDIS_URL=redis://localhost:6379

# Services
STRIPE_SECRET_KEY=
SESSION_SECRET=change-me
LOG_LEVEL="info"   # debug, info, warn, or error

--- actual ---
PORT=3000
NODE_ENV=development
export DATABASE_URL="postgres://app:app@localhost:5432/app_dev"
STRIPE_SECRET_KEY=
PORT=4000
DEBUG_SQL=1
log_level=debug
cache.ttl=60
SESSION SECRET=local-only
Output
Env file diff
Compares key names and emptiness only. Values from the actual file are not part of this report.

Summary
- Example file: 7 keys
- Actual file: 7 keys
- Missing from the actual file: 3
- Extra in the actual file: 3
- Duplicate keys in the actual file: 1
- Empty values in the actual file: 1
- Malformed lines in the actual file: 1

Missing from the actual file (3)
- REDIS_URL (example line 7, has a default)
- SESSION_SECRET (example line 11, has a default)
- LOG_LEVEL (example line 12, has a default)

Extra in the actual file (3)
- DEBUG_SQL (line 6)
- log_level (line 7)
- cache.ttl (line 8)

Possible case mismatches (1)
- LOG_LEVEL is missing, but the actual file has log_level on line 7

Duplicate keys in the actual file (1)
- PORT on lines 1 and 5

Empty values in the actual file (1)
- STRIPE_SECRET_KEY (line 4, also empty in the example file)

Malformed lines in the actual file (1)
- Line 9: The key contains a space or tab. Expected KEY=value with no whitespace inside the key.

Warnings (1)
- Actual file, line 8, cache.ttl: Not a portable shell variable name: contains a dot. POSIX shell names use only letters, digits, and underscores and do not start with a digit.

Paste-ready block (names and example defaults)
REDIS_URL=redis://localhost:6379
SESSION_SECRET=change-me
LOG_LEVEL="info"

Read the report from the summary down. Three keys are missing, so the block at the end holds REDIS_URL and SESSION_SECRET with their example defaults, and LOG_LEVEL with its quoted default. The inline comment after LOG_LEVEL in the example is not copied.

The case check notices that the actual file has log_level where the example has LOG_LEVEL. Those are different names. PORT is defined on lines 1 and 5, so one definition should go. STRIPE_SECRET_KEY is empty in both files, which usually means it still needs a real value.

The line SESSION SECRET=local-only is malformed because the key contains a space, which is why SESSION_SECRET also shows as missing. The key cache.ttl is flagged because a dot is not portable in a shell variable name. DATABASE_URL counts as present even though the actual file writes it with export and quotes.

How to use it

  1. Paste the example file

    Paste the contents of .env.example, .env.sample or .env.template into the example box. This file should hold key names and safe placeholder values, never real secrets.

  2. Paste your actual file

    Paste your .env into the actual box. If you would rather not paste real values, replace each non-empty value with x and leave empty values empty. The check only needs key names and whether a value is empty.

  3. Choose how to build the block

    Leave names only off to copy the example’s defaults into the block. Turn it on to get KEY= lines with nothing after the equals sign. Use names only if the example file might hold anything private.

  4. Read the sections

    Start with missing keys, then duplicates, empty values, and malformed lines. Extra keys are often fine: they may be local overrides, or the example may be out of date.

  5. Add the missing keys

    Copy the paste-ready block, add it to the end of your .env, and fill in real values. Check that the last line of the file ends with a line break first, or the first pasted key joins it.

  6. Check again

    Run the check once more. If you added a key the app needs, add it to .env.example too, so the next person starts from a complete file.

How it works

The checker reads both files with one parser, then compares key names. These are the rules, in the order they apply.

  1. Lines. CRLF, LF, and a lone CR each end a line, and a leading byte order mark is ignored. Blank lines are skipped. A line whose first non-blank character is # is a comment.
  2. Export prefix. An optional export followed by a space or tab is ignored, as python-dotenv documents.
  3. Keys. A key is the text before the first =, ignoring spaces and tabs around it. It may use ASCII letters, digits, underscores, dots, and dashes, and any other character, including a non-ASCII letter, makes the line malformed. A key with a dot or dash, or one that starts with a digit, is flagged as non-portable, because the POSIX definition of a shell name allows only letters, digits, and underscores and forbids a leading digit.
  4. Values. A value is the text after the first =, so it can contain more = characters. Unquoted values lose surrounding spaces and tabs.
  5. Inline comments. In an unquoted value, a # starts a comment only when a space or tab comes before it. That is the python-dotenv rule, and the Docker Compose env file format documents the same rule for a space. Node’s dotenv starts a comment at any # in an unquoted value, so the Node option applies that rule instead. Inside quotes a # is part of the value, and a comment may follow the closing quote.
  6. Quotes. Single and double quoted values keep their spaces. A backslash escapes the next character, so an escaped quote does not close the value. Line breaks are allowed only inside double quotes. Node’s dotenv supports multi-line values from version 15. python-dotenv documents them in single quotes too, and the Node and Docker Compose parsers read them there as well, but this checker reports them as malformed. A quote that never closes is reported as malformed, and parsing resumes on the next line.
  7. Empty values. A value is empty when nothing is left after quotes and comments are removed. KEY=, KEY="", and KEY= # note are empty. KEY=" " is not.
  8. Duplicates. Every line that defines the same key is listed. The empty check and the pasted default use the last definition, and the checker does not claim your loader does the same.
  9. Comparison. Keys match by exact name, so Api_Key and API_KEY differ. A missing key is in the example and not in the actual file. An extra key is the reverse.

The actual file is parsed without keeping values. The report shows only its key names, line numbers, and whether each value is empty. Nothing else from that file appears in the report, in an error message, or in the paste-ready block, which holds example defaults only.

Rig’s env_diff MCP tool draws the same line: it compares key names only and never returns values. It uses Rig’s own parser, so results can differ on unusual files.

Limitations

  • It compares key names and emptiness only. It does not check that a value is valid, and a placeholder such as change-me counts as filled in.
  • References to other variables are not expanded, so a value that points at an unset variable still counts as filled in. Docker Compose expands them in unquoted and double-quoted values, not in single-quoted ones.
  • Loaders do not all agree. Inline comments follow python-dotenv by default, with a Node option. Docker Compose reads KEY= # note as a value and does not end a value at a tab before #. Backticks are not quotes, and KEY: value and bare KEY lines are malformed.
  • It sees only the two files you paste, up to 200 KB and 5,000 lines each. A larger file is rejected, not truncated. Keys your app gets from .env.local, a shell export, a process manager, or CI secrets show up as missing here.
  • A multi-line value needs double quotes. Without them, each line is read on its own, so a line of a pasted private key can appear as a key name in the report. Replace such values with x before you paste.

Practical applications

Use it whenever two env files should agree and you cannot see why they do not.

  • Onboarding. A new teammate copies .env.example to .env and the app still fails. The diff shows which keys they never filled in.
  • After a pull. A branch added a config key. Compare your .env with the updated example to find what to add before the server starts.
  • Reviewing a pull request. A change reads a new variable. Check that .env.example gained the same key.
  • Works on my machine. Paste a working .env as the example and the broken one as the actual file to see exactly what differs.
  • Cleaning up. Duplicates and empty values hide in long files. The report lists the line numbers.
  • Docker Compose and CI. Files read through an env_file entry follow similar rules, so the malformed and duplicate checks still help.

Common mistakes

  • Pasting the real .env into the example box. The paste-ready block copies the example’s defaults, so real values would appear in it. Use names only if you are not sure what the example file holds.
  • Treating every extra key as an error. An extra key is often a local override or a debugging flag. Decide whether it belongs in the example file before you remove it.
  • Writing a value that contains a space and a # without quotes. After a space, # starts a comment, so ab #cd is read as ab. Quote any value that contains a #.
  • Fixing a missing key in your own .env and leaving .env.example behind. When you add a key the app needs, add it to the example file in the same change.
  • Assuming the last duplicate always wins. Which definition wins is up to the loader, so delete the one you do not want.

Frequently asked questions

What is the difference between .env and .env.example?

The .env file holds the real values for your machine and stays out of version control. The .env.example file lists the same key names with placeholders or safe defaults, and it is committed, so anyone who clones the project knows which keys to set.

Does this tool send my .env file anywhere?

No. The comparison runs in your browser and makes no network request. The report lists key names and line numbers, never values from your actual file. Even so, avoid pasting production secrets into any web page. Replace non-empty values with x first, because the check needs only key names and whether a value is empty.

Why is a key reported as missing when it is in my .env?

Check four things. The name may differ in letter case, because keys are case sensitive. The line may be commented out with a leading #. The line may be malformed, which the report lists with a line number. Or the key may sit inside a multi-line quoted value, where it is text, not a key.

Which duplicate definition wins?

It depends on the program that reads the file, so this checker does not guess. It lists every line that defines the key. Keep the definition you want and delete the others, so every loader sees the same value.

Why does the checker warn about dots and dashes in key names?

A POSIX shell name uses only letters, digits, and underscores and does not start with a digit. A loader may accept a key such as cache.ttl, but a shell cannot use it as a variable name, so the file breaks when anything sources it. The warning is advice, not an error.

Should I commit .env.example?

Yes. It holds key names and harmless placeholders, so it is safe to share and it documents what the app needs. Keep real secrets out of it. The dotenv documentation advises against committing the real .env file.

Sources

  1. dotenv for Node.js: README, parsing rules, comments, and multi-line valuesOfficial documentation, checked 13 October 2026
  2. Docker Compose: services reference, env_file formatOfficial documentation, checked 13 October 2026
  3. Docker Compose: variable interpolationOfficial documentation, checked 13 October 2026
  4. python-dotenv: file formatOfficial documentation, checked 13 October 2026
  5. POSIX Base Definitions: definition of a shell nameOfficial documentation, checked 13 October 2026
  6. POSIX Base Definitions: environment variablesOfficial documentation, checked 13 October 2026

Env profiles per lane in Rig

Rig keeps an env profile for each lane and masks values in its interface. Its env_diff MCP tool also compares key names only, so an agent can see which keys are missing without reading a secret.

Related

https://userig.app/tools/env-file-diff