How to Extract Environment Variables from Code, Scripts, and Configs
Pull every env var reference out of your code and scripts in one pass, dedupe the names, and turn the list into a complete .env.example. Runs locally.
How to Extract Environment Variables from Code, Scripts, and Configs
Every app I have ever inherited had the same problem: the code reads a dozen environment variables, and exactly three of them are written down anywhere. The rest live in someone's shell history, a Slack thread, or the head of an engineer who left in March. When a new hire clones the repo and runs it, the app crashes on the first missing variable, then the second, then the third, in a slow game of whack-a-mole.
The fastest way to end that game is to ask the code itself which variables it expects. Environment variable references are surprisingly easy to find because they follow a small set of fixed shapes. This post walks through those shapes, shows a worked example, and explains how to turn the raw list into a .env.example your whole team can rely on.
The four shapes an env var reference takes
Here is the one concrete fact that makes extraction reliable: env vars are referenced in several distinct styles, and almost everything you will ever encounter is one of these four.
process.env.NAME— Node.js, and the bracket formprocess.env['NAME']. Deno, Vite (import.meta.env.NAME), and Python'sos.environ['NAME']are close cousins.$NAME— POSIX shell, Makefiles, CI YAML, and most config templating. A bare dollar sign followed by an identifier.${NAME}— the braced shell form, used when a variable sits next to other characters, as in${DB_HOST}:${DB_PORT}. Docker Compose and Kubernetes manifests lean on this heavily.%NAME%— Windows batch files and some.ini-style configs.
The names themselves are constrained too. By convention they are uppercase letters, digits, and underscores, starting with a letter or underscore: DATABASE_URL, S3_BUCKET, STRIPE_SECRET_KEY. That tight shape is exactly why a scanner can find every reference without dragging in the surrounding code. The extractor pulls each referenced name out of context, drops the punctuation and the prose around it, and dedupes the result — so a variable used in fifteen files shows up once. From that single deduplicated list you can build a complete .env.example in a couple of minutes.
A worked example
Suppose you paste a slice of a real service into the Environment Variable Extractor. It is a mix of TypeScript, a shell entrypoint, and a Compose snippet — the kind of thing that lands in a code review:
// server.ts
const db = connect(process.env.DATABASE_URL);
const port = process.env.PORT || 3000;
const region = process.env['AWS_REGION'];
const key = process.env.STRIPE_SECRET_KEY;
# entrypoint.sh
: "${DATABASE_URL:?missing}"
echo "Serving on $PORT"
aws s3 ls "s3://$S3_BUCKET"
# docker-compose.yml
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- PORT=${PORT}
There are references in all four families mixed together, and several variables appear more than once. DATABASE_URL shows up four times, PORT three times. Run the extraction and the noise collapses to its unique names:
AWS_REGION
DATABASE_URL
PORT
REDIS_URL
S3_BUCKET
STRIPE_SECRET_KEY
Six variables. That is the whole configuration surface of this service, and it took one paste to find it. Notice what the scanner ignored: the literal 3000, the string Serving on, the function name connect, the s3 path. None of those match an env var shape, so none of them leaked into the list.
From the list to a .env.example
A flat list of names is already useful, but the artifact most teams actually want is a .env.example checked into the repo. The extractor lets you switch the output format, so instead of plain lines you can produce key-equals rows ready to become a template:
AWS_REGION=
DATABASE_URL=
PORT=
REDIS_URL=
S3_BUCKET=
STRIPE_SECRET_KEY=
Commit that file, leave the values blank (or fill the safe defaults like PORT=3000), and your README setup step shrinks to a single line: cp .env.example .env and fill in the secrets. The next person who clones the repo no longer plays whack-a-mole, because the contract is written down. When the code grows a new variable, you re-run the extraction, diff against the committed template, and add the one new line. That diff is the audit — it shows exactly which config keys changed between releases.
If you need other shapes, the same list drops straight into JSON, a SQL IN clause, a TypeScript union, or a Markdown table without hand-adding quotes and commas. For a follow-up cleanup pass — sorting, trimming stray whitespace, removing near-duplicates that differ only by case — the Environment Variable List Converter takes the extracted names and reshapes them into whatever your tooling expects, and the Environment Variable Deduplicator is handy when you are merging lists pulled from several services at once.
Three audits worth running today
Once you can pull a clean list in seconds, a few small audits pay for themselves quickly.
Which variables does this app actually need? Paste the whole src directory's grep output, extract, and you have the real dependency list — not the aspirational one in the wiki. I did this on a service I thought I understood and found it read LEGACY_API_HOST, a variable nobody had set in two years because the code path behind it was dead. Deleting that branch removed a config key and a chunk of confusion in the same commit.
What is undocumented? Diff the extracted set against your existing .env.example. Anything in the code but not in the template is undocumented config — the exact thing that breaks fresh checkouts. Anything in the template but not the code is dead config you can delete.
Is anything obviously mis-shaped? A reference like $DB HOST with a space, or %PATH missing its closing percent, usually means a typo that silently resolves to an empty string at runtime. Surfacing those mis-shaped catches early is cheaper than debugging a blank value in production.
Why this stays on your machine
Code and config are sensitive by definition. A Compose file or a CI script can carry hostnames, bucket names, internal service names, and sometimes a value that should never have been hard-coded in the first place. Extraction here runs entirely in the browser: the text you paste, or the local file you load, is read with the File API and parsed in the same browser tab. Nothing is uploaded to a server. That matters because the whole point of auditing config is to handle material you would not want to hand to a third party.
The names you pull out are still only names. A clean, deduplicated list tells you what the app references, not whether those services exist or whether the secrets behind them are valid — that is a separate check. But knowing the complete set of variables, written down and version-controlled, is the difference between a setup step that works on the first try and an afternoon of guesswork. Paste your code, take the list, and write the template once.
Made by Toolora · Updated 2026-06-13