---
url: https://findutils.com/guides/env-diff
title: "Find Missing Environment Variables: Compare .env With .env.example"
description: "Check which keys your .env has that .env.example lacks, and the other way round, before a deploy breaks. Compare two dotenv files by key in your browser."
category: developer
content_type: guide
guide_type: subtopic
cluster: configuration
locale: en
read_time: 7
status: published
author: "olgunozoktas"
published_at: 2026-09-25T12:00:00Z
excerpt: "A new key lands in someone's local .env, the code starts reading it, and .env.example never hears about it. The next person to set up the project, or the next server, starts without it. Comparing the two files by key finds that gap in seconds."
tag_ids: ["dotenv", "env", "configuration", "developer-tools", "secrets"]
tags: ["Dotenv", "Env", "Configuration", "Developer Tools", "Secrets"]
primary_keyword: "find missing environment variables"
secondary_keywords: ["compare .env and .env.example", "env file out of sync", "dotenv diff", "compare staging and production env", "env example missing keys"]
tool_tag: "env-diff"
related_tool: "env-diff"
related_tools: ["env-diff", "env-merge", "env-linter", "env-json-converter", "env-yaml-converter", "json-diff"]
og_image: "/images/content/guides/env-diff-cover-20260925.webp"
image_alt: "Two walnut key boards with brass hooks side by side; the right board has one empty hook and one key with a coral tag where the left board has plain keys."
updated_at: "2026-09-25T12:00:00Z"
---

To find environment variables missing from a project, compare your `.env` with `.env.example` by key name, not line by line. FindUtils [Env Diff](/developers/env-diff/) parses both files with the dotenv rules and lists keys only in one file and keys whose values differ, in your browser. The files are not uploaded, and secret-looking values are masked in the result by default.

This guide covers why the two files drift apart, which file to put on which side, how to read the result, and what the comparison deliberately does not do.

## Why .env and .env.example Drift Apart

The `.env` file holds real values and is kept out of the repository. The dotenv project's own answer to "should I commit my .env file?" is ["No"](https://github.com/motdotla/dotenv), and the [Next.js documentation](https://nextjs.org/docs/app/guides/environment-variables) warns that you almost never want to commit these files. That leaves `.env.example` (or `.env.template`, `.env.dist`) as the only shared record of which keys exist.

Drift happens because only one of the two files is used every day:

- **A developer adds a key locally.** The code reads it, their `.env` has it, and nobody updates the example.
- **A key is renamed.** `MAIL_HOST` becomes `SMTP_HOST` in code and in one person's `.env`; the example keeps the old name.
- **A key is retired.** The example still lists it, so new setups carry a value nothing reads.

None of these produce an error until someone starts from the example and the app fails at runtime.

## Which File Goes on Which Side?

Env Diff reads the comparison as a change from File A to File B. Keys only in B are **added**, keys only in A are **removed**, and keys in both with different values are **changed**.

| Question you are asking | File A | File B | What to act on |
|---|---|---|---|
| What is missing from the example? | `.env.example` | `.env` | Added keys: add them to the example with a placeholder |
| What does my setup lack? | `.env.example` | `.env` | Removed keys: add them to your `.env` |
| What differs between two servers? | staging file | production file | Changed keys, then added and removed |
| What did a branch change? | old `.env.example` | new `.env.example` | Everything listed |

The labels on each pane are editable, so the report can say "staging" and "production" instead of A and B.

## How to Compare the Two Files

### Step 1: Paste or drop both files

Open [Env Diff](/developers/env-diff/), paste `.env.example` into File A and `.env` into File B, or drop the files on the panes.

### Step 2: Choose what the result shows

**Values in the result** decides what is visible: mask secret-looking values (the default), mask all values, or show all values. **Ignore key case** treats `API_KEY` and `api_key` as one key. **Show unchanged keys** adds the keys that match to the list.

### Step 3: Compare and read the groups

Press Compare. The summary counts added, removed, changed and unchanged keys, and each group lists its keys. A changed key shows the old value and the new one.

### Step 4: Keep the report

Copy the report or download it as `env-diff-report.txt` or `env-diff-report.json`. Both downloads use the same masking as the screen, so a masked report is safe to paste into a ticket.

## What Counts as the Same Value?

Env Diff compares values after the dotenv rules are applied, the same way a dotenv loader would read them. These pairs compare as equal or different:

| File A line | File B line | Result |
|---|---|---|
| `PORT=3000` | `export PORT=3000` | Same: the `export` prefix is dropped |
| `NAME=app` | `NAME="app"` | Same: quotes are removed before comparing |
| `NAME=app # main` | `NAME=app` | Same: `#` after a space starts a comment on an unquoted value |
| `NAME=app#main` | `NAME=app` | Changed: a `#` with no space before it is part of the value |
| `MSG="a\nb"` | `MSG='a\nb'` | Changed: double quotes turn `\n` into a line break, single quotes keep it as two characters |
| `URL=$HOST/api` | `URL=db.example.com/api` | Changed: `$HOST` is compared as text, never expanded |

The order of lines never matters, because keys are matched by name. Comments and blank lines are ignored. A key written twice in one file keeps its last value, and a warning names both line numbers.

## How Masking Works, and Where It Stops

With the default setting, a value is hidden when its key name contains SECRET, TOKEN, PASSWORD, PASSWD, PWD, KEY, AUTH, PRIVATE, CREDENTIAL, SALT, DSN, COOKIE or SESSION, in any letter case. A masked value shows up to eight dots and its length, such as `•••••••• (40 chars)`, which is often enough to spot an empty or truncated secret.

The default also hides the password inside a connection URL under any key name: `DATABASE_URL=postgres://app:hunter2@db:5432/app` is shown as `postgres://app:••••@db:5432/app`, with the rest of the address readable. Beyond that, masking goes by the key name, and that cuts both ways:

- **Other secrets under plain names are shown.** An API token stored under `PAYMENTS` or a signed address in `WEBHOOK_URL=https://hooks.example.com/abc123` is visible, because neither name contains one of the words and neither value has a `user:password@` part. Choose Mask all values before taking a screenshot of such a file.
- **Harmless keys can be hidden.** `SESSION_LIFETIME=120` and `PUBLIC_KEY_PATH=./keys/pub.pem` are masked because their names contain SESSION and KEY. Choose Show all values when you need to read them.

Masked values are still compared on their real text, so a changed secret is reported as changed even when both sides show dots.

## Comparing Staging and Production

The same comparison works for any two dotenv files, not just a template and a copy. Save each environment's variables as a `KEY=value` file, put the one you trust in File A, and read the changed group first: a production value that differs from staging on a key like `APP_URL` or `QUEUE_CONNECTION` is usually on purpose, while one on a key like `CACHE_PREFIX` might not be.

Keep in mind that a framework may read more than one file. Next.js, for example, looks for a variable in `process.env`, then `.env.$(NODE_ENV).local`, `.env.local`, `.env.$(NODE_ENV)` and `.env`, [stopping at the first one that defines it](https://nextjs.org/docs/app/guides/environment-variables). Two `.env` files that match can still produce different settings if a `.env.local` or a variable set by the host sits above them. To compare the effective set, combine the layers first with [Env Merge](/developers/env-merge/) and compare the merged files.

## What the Comparison Does Not Do

- **It does not expand variables.** Next.js expands `$VARIABLE` references inside `.env` files; Env Diff compares the text as written.
- **It does not check that values work.** A wrong database password is compared like any other string. Nothing is fetched and no service is called.
- **It does not guess at broken lines.** A line with no `=`, an invalid key name or a quote that never closes stops the comparison, and the message names the file and the line number, so no key is invented.

For the problems inside one file, such as duplicate keys, unquoted values with spaces and invalid names, run it through [Env Linter](/developers/env-linter/) first.

## Common Mistakes

**Swapping the sides.** With `.env` in File A and the example in File B, the missing keys appear as removed instead of added. The keys are the same; only the labels flip. Set the pane labels so the report reads correctly.

**Pasting unmasked output into a chat.** The report you copy follows the masking setting. Check the setting before copying a comparison of real secrets.

**Treating "no differences" as "no problems".** Two files can have the same keys and both be wrong. The comparison answers which keys and values differ, nothing more.

## Tools Used in This Guide

| Tool | Use |
|---|---|
| [Env Diff](/developers/env-diff/) | Compare two .env files by key: added, removed, changed |
| [Env Merge](/developers/env-merge/) | Combine a base .env with an overlay before comparing layered setups |
| [Env Linter](/developers/env-linter/) | Check one .env for duplicate keys, invalid names and quoting problems |
| [Env JSON Converter](/developers/env-json-converter/) | Turn a .env into a flat JSON object, or JSON back into KEY=value lines |
| [JSON Diff](/developers/json-diff/) | Compare two JSON config files by value |

## FAQ

### How do I check that .env.example is up to date?

Compare `.env.example` (File A) with a working `.env` (File B) in Env Diff. Every key under Added is used in your `.env` but missing from the example. Add those keys to the example with a placeholder value, and remove any keys under Removed that the code no longer reads.

### Can I compare .env files that contain real secrets safely?

The files are read in your browser tab and are not uploaded. Secret-looking values and passwords inside connection URLs are masked in the result by default, and Mask all values hides every value before you screenshot or copy the report.

### Why does a key show as changed when the values look the same?

The parsed values differ. Common causes are a `#` without a space before it, which stays part of an unquoted value, single quotes that keep `\n` as two characters where double quotes make a line break, or trailing spaces inside quotes.

### Does Env Diff work with .env.local, .env.production and other names?

Yes. Any file with `KEY=value` lines in dotenv syntax works, whatever its name. Paste or drop it into either pane.

## Next Steps

Compare your `.env` against the example with [Env Diff](/developers/env-diff/). When you need one file from several layers, combine them with [Env Merge](/developers/env-merge/) first.
