dotenv-webpack
A secure Webpack plugin that loads dotenv variables into your bundle while exposing only what your code actually references.
Repository Health
Technical Analysis
dotenv-webpack wraps dotenv-defaults and Webpack’s DefinePlugin to bring environment variables from a .env file into your client bundle. Instead of injecting every variable in the file, it scans for process.env.[NAME] references in your source and only bakes in the ones your code explicitly uses — so secrets like database passwords or API keys that live in .env but are never referenced never end up in shipped JavaScript.
On top of the core load-and-replace behavior, it layers in .env.example “safe mode” validation (fail the build if a required variable is missing), .env.defaults fallback values, system environment variable merging for CI, and variable expansion/interpolation for values that reference other variables. Since Webpack 5 dropped its automatic Node.js polyfills, the plugin also stubs any remaining unreplaced process.env references so browser bundles don’t throw at runtime.
What You Get
- Compile-time replacement of
process.env.NAMEreferences using Webpack’sDefinePlugin, so only referenced variables reach the bundle - “Safe mode” via an
.env.exampleblueprint file that throws a build error when a required variable is missing or empty .env.defaultsfallback support (viadotenv-defaults) for shared default values across environments- Optional
systemvarsmerging so CI/CD environment variables are picked up alongside the.envfile - Variable expansion/interpolation (
${OTHER_VAR}syntax) for composing values from other variables - Automatic
process.envstubbing under Webpack 5+ targets that lack a Node.jsprocesspolyfill, avoiding runtime crashes in browser bundles - Configurable path, prefix (e.g. swap
process.env.forimport.meta.env.), and silent-mode logging
Common Use Cases
- Injecting API base URLs, feature flags, or public keys into a frontend bundle without exposing unrelated secrets from a shared
.envfile - Enforcing that all required environment variables are set before a CI build succeeds, using safe mode against
.env.example - Sharing one
.envfile across multiple packages in a monorepo, each with its own Webpack config pointing at a relative path - Migrating a Node-only codebase that used plain
dotenvinto a bundled frontend build without redoing environment variable handling
Under The Hood
Architecture
The entire plugin is a single Dotenv class in src/index.js implementing Webpack’s standard plugin interface via apply(compiler). Internally it follows a clear pipeline: gatherVariables() merges system environment variables, the parsed .env file, and optional .env.defaults values (validating against an .env.example blueprint when safe mode is enabled), then formatData() shapes the result into the key/value map Webpack’s DefinePlugin expects, applying variable interpolation and a Webpack-5-specific process.env stub before handing the assembled DefinePlugin instance to the compiler. There’s no build step or bundler needed to consume the plugin itself, and state is scoped entirely to the options passed into the constructor, so multiple instances in a monorepo don’t interfere with each other.
Tech Stack
Plain CommonJS JavaScript with a single runtime dependency, dotenv-defaults (itself a wrapper around dotenv adding defaults-file support), and a peer dependency on webpack ^4 || ^5. Type consumers get a hand-written types/index.d.ts using export = CJS-style declarations rather than a TypeScript build pipeline. Development tooling includes Jest 30 for testing, standard for zero-config linting enforced through a Husky precommit hook, and jsdoc for generating API docs from inline comments.
Code Quality
Tests in test/index.test.js run real Webpack compilations against a matrix of fixture .env files (empty, simple, one-empty, missing-one, systemvars, expanded, defaults) and assert on the literal replaced content of the emitted bundle rather than mocking Webpack internals, giving high confidence the plugin behaves correctly end-to-end. Coverage collection is enabled by default in the Jest config, and standard linting is enforced pre-commit. Error handling follows a deliberate two-tier pattern: silent-by-default warnings for missing .env files, but hard throws when safe mode is enabled and a required variable is absent — an explicit, documented choice rather than a swallowed failure.
API Design
The entire public surface is a single constructor option object with well-chosen defaults (path: './.env', prefix: 'process.env.'), so the plugin works with zero configuration in the common case. Every option is documented with JSDoc directly above the constructor, and advanced behaviors — safe mode, defaults files, system variable merging, expansion, a custom prefix for import.meta.env.-style access — are additive flags rather than separate APIs, keeping the boilerplate needed to get started minimal.
Used by 5 apps in this directory
Bigcapital
Invoicing Finance
Self-hostable double-entry accounting platform with invoicing, inventory, multi-currency, and real-time financial reporting for small and medium businesses.
Craft CMS
CMS
A developer-first PHP CMS with clean-slate content modeling, auto-generated GraphQL API, and a four-tier edition system that scales from solo projects to enterprise deployments.
Huly Platform
Project Management · Team Chat · Collaboration
Open-source all-in-one workspace that replaces Linear, Jira, Slack, and Notion for product and engineering teams.
Omnivore
Knowledge Management · Bookmarks Archiving · Note Taking
Self-hosted read-it-later platform with highlights, newsletters, PDFs, and seamless Obsidian and Logseq integration.
Portainer
Devops
A lightweight, open-source web UI that puts Docker, Kubernetes, and Podman management within reach of any team—no CLI expertise required.