mrkeyoor.com_
Sat 19 Sept 23:46 UTC
npmUtilsupdated 19 Sept 2026

dotenv review

dotenv 17.4.2 reads `.env` syntax from one file or an ordered list of files, then copies parsed strings into `process.env` or another object. Existing environment values take precedence unless `override` is enabled. `parse()` handles text without mutating the process, and `populate()` applies an already parsed object. The package belongs at Node startup: our CommonJS and ESM imports worked, while an esbuild browser build failed on its Node facilities. The 17.4.2 release only tightened the repository's coding-agent skill files; it made no parser or runtime change. The default branch now documents unreleased `dotenv run`, `secure`, and `fast` features, so those calls do not belong in code pinned to 17.4.2.

162.8Mdownloads / wk
Verdict

dotenv 17.4.2 installed in 0.4 seconds as a single 1 MB package, had 0 audit findings, and failed our browser build because it is Node startup code. Keep it for explicit file parsing on older or unopinionated Node stacks; remove it when the runtime or framework already loads env files, and do not copy unreleased CLI examples from the default-branch README.

We installed it

Lab card: what happened when we installed dotenvScreenshot of dotenv documentation
Install✓ · 0.4s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package with exports map
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does dotenv install cleanly?

Yes. In a fresh container with an empty cache, npm install dotenv finished in 0.4s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

Can dotenv run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does dotenv work with both ESM and CommonJS?

Yes. Both import 'dotenv' and require('dotenv') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does dotenv include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

dotenv or @dotenvx/dotenvx: which should you use?

@dotenvx/dotenvx: Use it for encrypted environment files, expansion, and command-oriented injection across languages. dotenv 17.4.2 installed in 0.4 seconds as a single 1 MB package, had 0 audit findings, and failed our browser build because it is Node startup code.

When should you not use dotenv?

Node's built-in --env-file behavior covers the project. Removing dotenv avoids an early startup import and its default log line.

API stability4/5The stable package still centers on `config`, `configDotenv`, `parse`, and `populate`, and it preserves the long-standing rule that existing environment variables win unless `override` is true. Major 17 changed the default logging behavior, so silent tools must opt into `quiet`. The default branch is already removing preload and vault behavior while adding a CLI and new parser, which makes tag-specific reading necessary even though 17.4.2 itself only changed skill documentation.
Docs3/5The v17.4.2 README explains ESM initialization order, preload flags, custom paths, arrays of files, comments, multiline values, parsing, override behavior, and custom targets with runnable examples. The main README has moved ahead of the npm stable package and now presents unreleased CLI, secure, and fast options as ordinary usage. That version drift is serious enough that a reader can copy valid repository documentation and still get an unknown command or ignored option from 17.4.2.
Maintenance4/5npm published 17.4.2 on 2026-04-12, GitHub records a push on 2026-08-04, and the unarchived repository shows 4 open issues and pull requests. Work after the tag includes a CLI, parser changes, and removal of old preload and vault paths. Active development is clear, but the gap between released code and default-branch instructions makes the current maintenance state harder for stable users to interpret.
Ecosystem5/5npm counted 176,453,926 downloads in the latest completed week, and GitHub shows 20,525 stars. The `.env` file shape is shared by runtimes, frameworks, containers, and hosting platforms, while companion packages cover expansion and file layering. That reach also creates overlap: Node has `--env-file`, many frameworks preload configuration, and production platforms inject environment values without dotenv at all.

Discussed on

  1. hnShow HN: From dotenv to dotenvx – better config management354 points
  2. hnShow HN: Dotenv, if it is a Unix utility225 points
  3. hnWe Are Forking dotenvy into dotenv-ng46 points
  4. hnShow HN: Dotenv Mask Editor: No more embarrassing screen leaks of your .env28 points
  5. hnZdotenv – Dotenv Loader for Zig28 points

Use it if

  • A Node process needs local `.env` loading and cannot rely on Node's native `--env-file` flag or a framework loader.
  • Startup needs an explicit file order, a non-UTF-8 encoding, override control, or a destination other than `process.env`.
  • A build tool must parse dotenv text into an object without changing the current process.
  • An established application already depends on dotenv's parsing and collision rules, and replacing them would change local setup.
Skip it if

Setup reality

We installed dotenv 17.4.2 in a fresh Node 22 Bookworm sandbox in 0.4 seconds. It left 1 package and 1 MB on disk. npm audit reported 0 vulnerabilities at all severities. The package has 0 direct and 0 peer dependencies, occupies 156 KB unpacked, requires Node 12 or newer, and uses BSD-2-Clause. TypeScript declarations are bundled. require() and ESM import both worked through its exports map. Our esbuild browser bundle failed, which keeps dotenv on the Node side of an application.

The default lookup is path.resolve(process.cwd(), '.env'), so a process manager's working directory matters. Load it before modules that read configuration during import. For ESM, import 'dotenv/config' executes early enough; calling config() above another static import does not, because imported modules evaluate before the entry module body. Version 17.4.2 also supports node -r dotenv/config app.js and DOTENV_CONFIG_* preload options.

Existing process.env values win by default. With path: ['.env.local', '.env'], the first file's value wins too. Turning on override reverses both choices, so the last file can replace earlier files and file values can replace the deployment environment. Version 17 prints its injected-key message to stdout unless quiet: true is set, which can break a CLI that promises JSON or another machine-readable stream.

A normal .env is plaintext and should stay out of source control. Quoted multiline values and \n inside double quotes are parsed, while # starts a comment unless the value is quoted. All results are strings; dotenv does not check required names, convert booleans, or expand ${OTHER_KEY}. Inspect the returned error when a requested file may be missing, then validate the resulting configuration before starting listeners or workers.

Patterns

Load `.env` before application startup load-default-env

const result = require('dotenv').config({ quiet: true })

if (result.error) throw result.error
startServer({ port: Number(process.env.PORT) })

`config()` resolves `.env` from `process.cwd()` and returns an `error` when loading fails. `quiet: true` suppresses version 17's stdout message.

Initialize dotenv before ESM dependencies preload-esm-env

// index.mjs
import 'dotenv/config'
import { startServer } from './server.mjs'

startServer()

The side-effect import evaluates before `server.mjs`. Calling `config()` in the entry module body can be too late for values read during another module's initialization.

Load configuration without editing the entry file preload-from-node

node -r dotenv/config server.js

# pass preload options
DOTENV_CONFIG_PATH=./config/dev.env \
DOTENV_CONFIG_QUIET=true \
node -r dotenv/config server.js

Version 17.4.2 supports Node's `-r dotenv/config` preload path. The separate `dotenv run` command belongs to unreleased repository code, not this package version.

Resolve a file beside an ESM module load-custom-path

import dotenv from 'dotenv'

const result = dotenv.config({
  path: new URL('../config/service.env', import.meta.url),
  quiet: true,
})
if (result.error) throw result.error

The 17.4.2 declarations accept a string, string array, or URL for `path`. An explicit URL avoids dependence on the launch directory.

Layer a local file ahead of defaults merge-env-files

require('dotenv').config({
  path: ['.env.local', '.env'],
  quiet: true,
})

Without `override`, the first file wins each duplicate key and existing `process.env` values beat both files.

Make later files replace earlier sources override-existing-values

require('dotenv').config({
  path: ['.env', '.env.local'],
  override: true,
  quiet: true,
})

With `override: true`, the last file wins and can also replace values already present in `process.env`. Use this only with a deliberate precedence policy.

Turn dotenv text into an object parse-without-mutation

import { parse } from 'dotenv'

const parsed = parse(Buffer.from([
  'HOST=127.0.0.1',
  'PORT=3000',
].join('\n')))

console.log(parsed) // { HOST: '127.0.0.1', PORT: '3000' }

`parse()` returns strings and does not modify `process.env`. Convert numbers and booleans during a separate validation step.

Keep parsed values out of `process.env` use-custom-target

const dotenv = require('dotenv')
const runtimeConfig = {}

const result = dotenv.config({
  path: '.env.worker',
  processEnv: runtimeConfig,
  quiet: true,
})
if (result.error) throw result.error

`processEnv` changes the destination object. The same collision and `override` rules apply to keys that already exist on that object.

Apply an approved parsed object populate-selected-values

import { parse, populate } from 'dotenv'
import { readFileSync } from 'node:fs'

const parsed = parse(readFileSync('.env'))
const selected = { API_URL: parsed.API_URL }
const applied = populate(process.env, selected)

console.log(Object.keys(applied))

`populate()` returns only the keys it actually wrote. Existing target values remain unchanged unless its options include `override: true`.

Store a multiline secret correctly handle-multiline-value

# .env
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nline-one\nline-two\n-----END PRIVATE KEY-----"

# application
const { config } = require('dotenv')
config({ quiet: true })
console.log(process.env.PRIVATE_KEY.split('\n').length)

Double-quoted `\n` sequences become newline characters. Single-quoted and unquoted values do not receive that newline expansion.

Quote values containing a hash preserve-hash-character

# .env
UNQUOTED=value#comment
QUOTED="value#data"

Version 17 treats `#` as the start of a comment outside quotes. The two entries parse as `value` and `value#data` respectively.

Reject missing or malformed settings after loading validate-required-config

import 'dotenv/config'

const port = Number(process.env.PORT)
if (!Number.isInteger(port) || port < 1 || port > 65535) {
  throw new Error('PORT must be an integer from 1 to 65535')
}
if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL is required')
}

dotenv only loads strings; it does not enforce presence, types, URL syntax, or numeric ranges. Validate before starting network listeners.

Alternatives

PackageRegistryPick it when
@dotenvx/dotenvxnpmUse it for encrypted environment files, expansion, and command-oriented injection across languages.
dotenv-flownpmUse it when `.env.local` and environment-specific layers should be discovered automatically.
dotenv-expandnpmUse it alongside dotenv when values must interpolate other environment variables.
env-cmdnpmUse it when the main requirement is launching a command with variables from a chosen file.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.