CLI tool
vlucas/phpdotenv avatar
vlucas/phpdotenv

vlucas/phpdotenv: loading .env files into PHP super-globals

Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.

13,552 stars666 forksPHPBSD-3-Clause

At a glance

What is it?
PHP dotenv reads a .env file and writes the values into $_ENV and $_SERVER, with getenv() available only through the unsafe loaders. It is aimed at PHP applications that want twelve-factor configuration without touching virtual hosts or .htaccess.
Who is it for?
Adopt vlucas/phpdotenv if you have a PHP application that already reads configuration from $_ENV or $_SERVER and you want per-environment values kept out of version control. Do not adopt it if your code calls getenv() everywhere and you are unwilling to switch to createUnsafeImmutable, or if you only need a handful of constants and a plain PHP config file would do.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 36 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem vlucas/phpdotenv solves for PHP deployments

PHP applications have historically been configured in places that live outside the project: Apache or nginx virtual host blocks, php_value lines in .htaccess, or hard-coded constants. The README frames the alternative directly, listing what you no longer have to do: no editing virtual hosts, no adding php_value flags to .htaccess, and the same variables available whether the app runs under Apache, nginx, the CLI, or PHP's built-in web server. That last point matters more than it sounds. A cron job and a web request under the same codebase will see the same configuration without a second setup step.

The intended audience is a PHP project that follows the twelve-factor idea of storing configuration in the environment. The README states the rule plainly: sensitive credentials should never be stored in code, and anything likely to change between deployment environments, such as database credentials or third-party service keys, should be extracted into environment variables. A .env file is the local carrier for those values.

The workflow the README describes is a pair of files. .env holds the real values and stays out of version control, added to .gitignore. .env.example holds the same variable names with blank or dummy values and is committed, so collaborators can see which variables are required without seeing production secrets. Each collaborator copies .env.example to .env and fills in their own values. This is a convention, not enforcement. Nothing in the library checks that .env is ignored, and nothing stops a developer from committing it.

How the immutable and mutable loaders differ

The library reads a file and writes the parsed values into PHP's super-globals. The README says all defined variables become available in both $_ENV and $_SERVER, so $_ENV['S3_BUCKET'] and $_SERVER['S3_BUCKET'] return the same string.

The choice that shapes most integrations is between the immutable and mutable loaders. Dotenv::createImmutable returns a loader that will not overwrite a variable already present in the environment. createMutable will overwrite. In a container or a CI pipeline where the platform injects real production variables, immutable is the safer default: the platform wins and the file cannot silently replace a value that operations set deliberately.

getenv() is a separate question. The README is blunt about it: using getenv() and putenv() is strongly discouraged because those functions are not thread safe. The library still supports them. Calling Dotenv::createUnsafeImmutable instead of createImmutable, or createUnsafeMutable instead of createMutable, attaches a PutenvAdapter behind the scenes, after which getenv('S3_BUCKET') works alongside the super-globals. The word unsafe in the method name is the library telling you the trade-off rather than hiding it. If your framework or a dependency calls getenv() internally, you need one of the unsafe loaders; there is no way to populate getenv() without them.

Nesting, quoting and how references are resolved

Values can reference other values. The README's example defines BASE_DIR and then builds CACHE_DIR and TMP_DIR from it using the ${...} form. The syntax rules are narrow and worth reading twice. A bare $BASE_DIR is never interpolated; only ${BASE_DIR} is. Interpolation happens in unquoted and double-quoted values but never inside single-quoted ones, and inside double quotes a reference can be escaped as \${BASE_DIR} when you want the literal text.

The failure mode is quiet rather than loud. If a referenced variable is not defined, the reference is left in place verbatim instead of becoming an empty string. A typo in a variable name therefore produces a value like ${BASE_DIR}/cache sitting in your configuration, which may only surface when the path is used at runtime. References resolve from right to left, so a resolved inner reference can form part of an outer one.

Resolution reads from the repository being loaded into, not just from the file. Under createImmutable, a variable already present in $_SERVER or $_ENV takes precedence over the value defined in the file. Under createUnsafeImmutable, values visible through getenv() and putenv() are read and protected as well. That is a coherent design, but it means the effective value of a variable depends on the loader you chose and on what was already in the process environment before load() ran. When debugging a value that does not match the file, check the loader first.

Installing vlucas/phpdotenv and loading a first file

The README gives Composer as the installation route. The package name is vlucas/phpdotenv, so a single command pulls it in and registers it with Composer's autoloader. If you prefer to manage dependencies by hand, the README also notes you can add it directly to your composer.json file.

bash
composer require vlucas/phpdotenv

After installation, create a .env file in the root of your project. The README's example uses two variables, S3_BUCKET and SECRET_KEY, each written as a quoted name-value pair. The README stresses that this file must be in .gitignore so it is never checked in.

shell
S3_BUCKET="dotenv"
SECRET_KEY="souper_seekret_key"

Then create a .env.example with the same variable names but blank or dummy values and commit that instead. The point is to advertise which variables are required without publishing the production values.

shell
S3_BUCKET="devbucket"
SECRET_KEY="abc123"

Loading happens in two lines. createImmutable takes the directory holding the file, and load() reads it. After that, the values are in $_ENV and $_SERVER, so $_ENV['S3_BUCKET'] returns what you wrote.

php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

If a missing .env file should not be fatal, the README offers safeLoad() in place of load(). That suppresses the exception thrown when no .env file exists, which is useful in production where the real variables come from the platform and no file is present at all.

php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();

The loader is also configurable. A second parameter selects a filename other than .env. Both the directory and the filename may be arrays, in which case only the first readable file is loaded by default; passing false as the third parameter merges every readable file instead, with later files overriding earlier ones. A fourth parameter sets the file encoding. The README's example loads .env and .env.local as a merged pair with UTF-8 encoding.

php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, ['.env', '.env.local'], false, 'UTF-8');
$dotenv->load();

Where vlucas/phpdotenv is the wrong tool

The library loads a file. It does not fetch secrets from a vault, rotate credentials, or audit who read what. If your threat model requires secrets to be short-lived or centrally revoked, a .env file on disk is the wrong storage layer, and phpdotenv is only the reader for a mechanism you have already decided against.

The thread-safety caveat is a real limitation, not a footnote. The README says getenv() and putenv() are strongly discouraged because they are not thread safe, and the only way to reach getenv() is to opt into a loader whose name contains Unsafe. On a conventional PHP-FPM or CLI setup this rarely bites. Under a threaded SAPI, or any runtime where requests share a process and mutate the environment, the unsafe loaders are a genuine hazard, and code that depends on getenv() is the thing to change.

There is also a structural mismatch with frameworks that already ship their own environment handling. Laravel, for instance, bundles its own env() function and environment loading, so adding vlucas/phpdotenv on top in a Laravel application is redundant. The library is for applications that do not already have this layer.

Finally, the mutable loaders invite a specific class of bug. If createMutable overwrites whatever the process environment already contains, a stale .env file left on a server can silently replace values that operations set. The immutable default exists to prevent exactly that, and choosing mutable should be a deliberate decision rather than a convenience.

vlucas/phpdotenv compared with Symfony Dotenv

The closest alternative in the PHP ecosystem is Symfony's Dotenv component, which is widely used through Symfony's runtime bootstrap. The difference in approach is about who owns the environment. Symfony's component is designed to be driven by the framework's runtime: it is wired into the application bootstrap and works with Symfony's notion of environments, debug flags, and .env.local style overrides. If you are building on Symfony, using its component keeps you inside the framework's conventions and avoids loading the same file twice.

vlucas/phpdotenv is framework-agnostic. It takes a directory and a filename, reads the file, and populates $_ENV and $_SERVER. There is no runtime, no environment concept beyond what you pass in, and no assumption about the rest of your stack. That makes it a better fit for a plain PHP application, a legacy codebase being moved toward twelve-factor configuration, or a library that wants to offer .env support without pulling in a framework. The cost of that neutrality is that you assemble the surrounding conventions yourself: which files load, in what order, and whether missing files are fatal.

The two also differ in how they treat the process environment. phpdotenv's immutable loaders make the existing environment authoritative, and its README documents the precedence explicitly. Symfony's component has its own rules for which variables win, tied to its runtime behavior. Neither is wrong; the point is that the precedence rules differ, so a mixed setup where both loaders touch the same process is a configuration bug waiting to happen.

Maintenance, upgrading and the BSD 3-Clause licence

The repository is not archived, and the last push was on 2026-08-24, which is the same date as the v5.7.0 release. The two releases before that were v5.6.4 on 2026-07-06 and v5.6.3 on 2025-12-27. So the release cadence is irregular: a patch in late December 2025, a patch in July 2026, then a minor release in August 2026. Nothing visible in the repository suggests a long gap, but the spacing does mean you should not expect a steady stream of changes.

Upgrade cost is governed by semantic versioning, which the README states the project follows, with the consequence that breaking changes may occur between major releases. The repository carries an UPGRADING.md file, and the README points to guides covering V2 to V3, V3 to V4 and V4 to V5. That is a meaningful maintenance signal: a major-version bump comes with a written migration path rather than a changelog entry. The practical cost of staying current is reading the relevant section of UPGRADING.md before bumping the constraint in composer.json.

The licence is BSD-3-Clause. For most PHP applications that is permissive enough to use in closed-source products, and it carries no copyleft obligation to publish your own code. The clause to be aware of is the third one, which prohibits using the names of the copyright holders or contributors to endorse or promote derived products without permission. This is a description of the licence text, not legal advice; if your organisation has a licence review process, run the decision past it.

Editorial conclusion

Adopt vlucas/phpdotenv if you have a PHP application that already reads configuration from $_ENV or $_SERVER and you want per-environment values kept out of version control. Do not adopt it if your code calls getenv() everywhere and you are unwilling to switch to createUnsafeImmutable, or if you only need a handful of constants and a plain PHP config file would do. Before rolling it out, verify three things: that .env is listed in .gitignore, that no committed file contains real credentials, and that your deployment sets the production variables in the real process environment, since createImmutable will not overwrite them.

Frequently asked questions

What is vlucas/phpdotenv?

It is a PHP library that loads environment variables from a .env file into $_ENV and $_SERVER, and optionally into getenv(). The README describes it as a PHP version of the original Ruby dotenv.

How do I install phpdotenv?

Install it with Composer using the package name vlucas/phpdotenv. The README also notes you can add it by hand to your composer.json file instead.

How do I use phpdotenv in a PHP application?

Create a .env file in your project root, then call Dotenv::Dotenv::createImmutable(__DIR__) and load() on the result. After that the values are readable from $_ENV and $_SERVER.

How does vlucas/phpdotenv compare with Symfony Dotenv?

Symfony's component is driven by the framework's runtime and its environment conventions, while vlucas/phpdotenv is framework-agnostic: you pass a directory and filename and it populates the super-globals. The precedence rules for existing environment variables also differ between the two.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. README
  4. Releases
  5. vlucas/phpdotenv on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/vlucas-phpdotenv.svg)](https://hysenlabs.com/projects/vlucas-phpdotenv)