.env Files Explained: Syntax, Quoting and the Mistakes That Bite

Everything in a .env file is a string, including the word false. That one fact explains most configuration bugs in this area.

.env Files Explained: Syntax, Quoting and the Mistakes That Bite
Share this article:

A `.env` file looks like the simplest possible format: `KEY=value`, one per line. It has more edge cases than that suggests, and one property that causes a specific, recurring bug.

Everything is a string

Environment variables have exactly one type. This line:

DEBUG=false

sets `DEBUG` to the string `"false"`. And a non-empty string is truthy in JavaScript, Python, Ruby, PHP and most other languages. So this does not do what it appears to:

if (process.env.DEBUG) {
  enableVerboseLogging()   // runs even when DEBUG=false
}

The only way to turn it off is to remove the variable entirely or set it empty. Coerce explicitly instead:

const DEBUG = process.env.DEBUG === 'true'
const PORT = Number(process.env.PORT) || 3000

The same applies to numbers. `PORT=3000` gives you `"3000"`, and `"3000" + 1` is `"30001"`.

The quoting rules

Three forms, three behaviours:

LineResulting valueNotes
`KEY=hello world``hello world`Unquoted; works but fragile
`KEY="hello world"``hello world`Quotes removed
`KEY='hello world'``hello world`Quotes removed, taken literally
`KEY="line1\nline2"`two lines`\n` expanded in double quotes
`KEY='line1\nline2'`literal `\n`not expanded in single quotes
`KEY=value # comment``value`inline comment stripped
`KEY="value # hash"``value # hash`hash kept inside quotes

The single-versus-double distinction matters when a value contains ` .env Files Explained: Syntax, Quoting and Common Mistakes . Some loaders expand `${OTHER}` inside double quotes and not inside single quotes — a password containing a dollar sign can silently become an empty string. When in doubt, single-quote secrets.

Comments and export

Lines beginning with `#` are ignored. A leading `export ` is stripped, which exists so the file can also be `source`d by a shell:

# Database configuration
export DATABASE_URL=postgres://localhost:5432/app
API_KEY='sk-live-1234567890'

Both variable lines are equivalent to a dotenv loader.

Spaces around the equals sign

KEY = value      # wrong in most loaders
KEY=value        # right

Shell syntax does not allow spaces around `=`, and most dotenv implementations follow suit. The spaces end up in the key, the value, or both — and the resulting bug looks like the variable simply is not set.

Converting to and from JSON

Deployment platforms and CI systems usually want JSON, while local development wants a `.env`. The ENV to JSON converter goes both ways, handling comments, export prefixes and quoting.

It runs in your browser, which matters here — a `.env` file is by definition the file with your secrets in it. Even so, prefer exporting directly from a secrets manager where you can, and clear the field afterwards.

Common mistakes

  • Committing the file. `.env` belongs in `.gitignore`. If you have already committed secrets, rotate them — deleting the file in a later commit leaves it in history.
  • Assuming nesting works. Dotenv is flat. There is no `DATABASE.HOST` structure; the convention is `DATABASE_HOST`. To go from nested config, flatten it first with the JSON flattener.
  • Multi-line values without quotes. A private key spanning several lines needs double quotes with `\n` escapes, or only the first line is read.
  • Expecting load order to matter. Variables are a flat namespace with no ordering guarantees. One line cannot reliably depend on an earlier one unless your loader explicitly supports interpolation.
  • Relying on `.env` in production. It is a development convenience. Production secrets belong in your platform secret store, where they are encrypted and access-controlled.

Frequently asked questions

Should .env be committed?

Never. Commit a `.env.example` with the keys and placeholder values so others know what to set.

Why is my variable undefined?

Usual causes: the loader runs after the code reading the variable, spaces around the equals sign, the file is in the wrong directory, or the process was not restarted after editing.

Does the order of variables matter?

No, unless your loader supports interpolation — and even then, relying on it is fragile.

Can values contain the equals sign?

Yes. Only the first `=` splits the line, so `KEY=a=b` gives `a=b`. This matters for base64 values, which often end in `=`.

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

Convention, not standard. Most frameworks load `.env` first, then `.env.local` to override it, and gitignore only the latter. Check your framework's documentation.

Related reading

For nested configuration that needs to become flat keys, the JSON flattener does the conversion. If your config includes API tokens, the JWT decoder inspects claims locally so you can check an expiry without pasting a token into a third-party site.

Convert .env to JSON →