How to Convert a .env File to JSON Without Leaking Secrets
A practical guide to turning a .env file into JSON: how KEY=value lines parse, why comments and blank lines drop, and how to feed config into JSON tools locally.
How to Convert a .env File to JSON Without Leaking Secrets
A .env file is the lowest-friction way to keep configuration out of your code. Twelve lines of KEY=value, no schema, no ceremony. The trouble starts the moment something downstream wants JSON instead: a config loader that reads config.json, a test fixture, a script that expects an object it can index by key. You could retype every line by hand, but that is exactly where a stray quote or a mangled connection string sneaks in.
Converting .env to JSON is mechanical once you know the rules dotenv actually follows. This guide walks through those rules, shows a worked example, and explains why the conversion should happen on your machine and nowhere else.
What a .env file really is
Strip away the mystique and a .env file is a list of lines. Each meaningful line is a KEY=value pair. Three kinds of lines carry no data and get dropped during conversion:
- Full-line comments that start with
#. - Blank lines, including lines that are only whitespace.
- The
exportprefix, which Bash needs but JSON does not, soexport API_KEY=abcparses identically toAPI_KEY=abc.
Everything else becomes one entry in the output object. The concrete rule is simple: each KEY=value line becomes one JSON key and value, comments starting with # and blank lines are skipped, and the rest is preserved verbatim. That single sentence covers ninety percent of real files.
The remaining ten percent is where naive splitters fail. A line like TOKEN=ab=cd=ef has three equals signs, but only the first one splits key from value, so the value is ab=cd=ef. A database URL such as DATABASE_URL=postgres://app:pw@db:5432/api?sslmode=require is full of colons, slashes, and its own =, and all of it belongs to the value. If your parser splits on every =, you have already corrupted the most important line in the file.
Quotes, comments, and the edge cases that bite
Quoting is where dotenv stops being trivial. Double-quoted values expand escapes, so GREETING="hi\nthere" becomes a real two-line string. Single-quoted values are literal, so PATH='C:\temp' keeps the backslash exactly as typed. After a closing quote, a trailing # comment is ignored.
Inline comments have one rule people forget: the # needs a space before it to count as a comment. So PORT=3000 # http port yields 3000, but COLOR=#ff0000 keeps the full hex value, because there is no space before the hash. A # inside quotes is never treated as a comment at all. These are not arbitrary choices; they match how Node's dotenv and Bash read the same file, which is what you want if the JSON is meant to mirror what your app sees at runtime.
There is also the question of types. By default every value stays a string, because that is precisely how process.env behaves: process.env.PORT is "3000", not 3000. If the JSON consumer expects real types, turn on type inference and PORT=3000 becomes the number 3000 while DEBUG=true becomes the boolean true. A conservative converter leaves leading-zero values like 01234 as strings, since those are almost always account IDs you do not want silently turned into integers.
A worked example
Here is a small, realistic .env:
# Database
export DATABASE_URL=postgres://app:pw@db:5432/api?sslmode=require
PORT=3000 # http port
APP_NAME="My App"
COLOR=#ff0000
DEBUG=true
Run that through the .env to JSON Converter with type inference off, and you get:
{
"DATABASE_URL": "postgres://app:pw@db:5432/api?sslmode=require",
"PORT": "3000",
"APP_NAME": "My App",
"COLOR": "#ff0000",
"DEBUG": "true"
}
Trace what happened line by line. The # Database comment and the blank line before DEBUG both vanished. The export prefix on DATABASE_URL was stripped, and the ?sslmode=require query string came through untouched even though it contains an =. PORT=3000 # http port lost its inline comment because of the space before #, leaving 3000. COLOR=#ff0000 kept its hash because there was no space. The double quotes around My App were consumed, leaving a clean string with a space in it. Five keys in, five keys out.
Flip type inference on and PORT becomes the number 3000 and DEBUG becomes the boolean true, while COLOR and the URL stay strings. That is usually the version you want when the JSON feeds a typed config object or a numeric assertion in a test.
Feeding the JSON into tools that want it
Once your config is JSON, it travels well. A legacy service that reads a flat config.json can take the output as-is. A test fixture can import it directly, so your suite runs against the exact values your app reads without parsing dotenv at runtime. A CI step that templates settings can splice it into a larger payload.
If you want to pretty-print, validate, or re-indent the result before committing it, pass it through the JSON formatter — it will catch a trailing comma or a stray quote before the file lands in your repo. And the conversion runs both directions: when a dashboard hands you settings as a JSON blob but your deploy expects a .env, switch direction and you get KEY=value lines back, with values that contain spaces correctly quoted and numbers left bare.
Why this has to stay local
I keep a .env for a side project that holds a live database password and a payment-provider key. The first time I needed it as JSON, my instinct was to paste it into whatever search result came up first — and then I stopped, because pasting a production secret into a random web form is how credentials end up in someone else's server log. That single hesitation is the whole reason I care about where conversion happens.
Parsing a .env, expanding quotes, inferring types, and flattening JSON back to dotenv are all plain string operations. None of them need a server. A .env almost always holds real credentials, so the conversion should run as JavaScript inside your own browser tab, with nothing uploaded and nothing logged. The one thing to watch: a shareable link encodes your input in the URL, which means it lands in the recipient's access log if you paste it into chat. For anything live, use the copy button and paste the text — never put a real API key into a public share link.
The short version
Each KEY=value line becomes one JSON entry. Lines that start with # and blank lines are skipped. Only the first = splits a line, quotes are honored, and inline comments need a space before the hash. Keep types off when you want string-faithful output that mirrors process.env, and turn them on when a consumer needs real numbers and booleans. Do all of it locally, because the file in front of you is usually a list of secrets.
Made by Toolora · Updated 2026-06-13