Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dotenv Updater Laravel Package

codezero/dotenv-updater

A Laravel package for programmatically updating and managing .env file variables. Modify environment values from code without manual editing, while preserving the file structure and simplifying common dotenv configuration tasks.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a framework-agnostic, single-purpose package: it rewrites key/value pairs inside a .env file. There is no service provider, no facade, and no config to publish — which is good news for a Laravel app, because it means installation is one line and there is nothing to wire up.

composer require codezero/dotenv-updater

The first place to look is src/ — the public surface is deliberately tiny (a constructor that takes the path to a .env file, plus a handful of read/write methods). Read src/DotEnvUpdater.php before the README; at this package size the class is the documentation, and you will want to know exactly how it matches and rewrites lines (see Gotchas) before you point it at a real .env.

Minimal usage from anywhere inside a Laravel app:

use CodeZero\DotEnvUpdater\DotEnvUpdater;

$updater = new DotEnvUpdater(base_path('.env'));

$updater->update('APP_URL', 'https://staging.example.test');

The first use case a Laravel developer hits is almost always an install/setup flow: an artisan command or a one-off installer that generates an APP_KEY, collects database credentials from a prompt or a form, and writes them back. That is the sweet spot — one process, one file, no concurrency, and a human watching the output.

Second most common: seeding a fresh checkout — copy .env.example to .env, then programmatically flip a few values (APP_ENV, APP_DEBUG, DB_DATABASE) as part of a provisioning script.

Implementation Patterns

Artisan command wrapper. Wrap the updater in a command so it inherits Laravel's path helpers, output, and confirmation prompts:

class SetEnvCommand extends Command
{
    protected $signature = 'env:set {key} {value}';

    public function handle(): int
    {
        $path = base_path('.env');

        if (! is_writable($path)) {
            $this->error("{$path} is not writable.");
            return self::FAILURE;
        }

        (new DotEnvUpdater($path))->update($this->argument('key'), $this->argument('value'));

        $this->call('config:clear');
        $this->info('Updated.');
        return self::SUCCESS;
    }
}

Idempotent writes. Check before you write. Every write is a file mutation, and rewriting a value that is already correct creates noisy diffs and unnecessary mtime churn. Read the current value (or just diff the file after) and bail early. This matters most in deploy scripts that run on every release.

Setup wizard. For an interactive installer, collect input, validate it, write all keys in one pass, then run config:clear and key:generate. Keep the write phase last and wrapped so a validation failure never leaves a half-written .env.

Bootstrapping from .env.example. Copy the template, then update individual keys rather than regenerating the whole file. This preserves comments and ordering in the example file, which is a real benefit — a fully regenerated .env loses all the explanatory comments your team relies on.

Testing. Never point tests at the real .env. Write a fixture into storage_path('framework/testing/'), run your updater against that path, and assert on the file contents. Inject the path (constructor argument is already a path, so this is trivial) rather than resolving base_path() inside the class you are testing.

Binding. If you use it in more than one place, bind a singleton factory in a service provider that resolves base_path('.env') once, so call sites do not repeat path logic:

$this->app->bind(DotEnvUpdater::class, fn () => new DotEnvUpdater(base_path('.env')));

Integration tip: pair every write with Artisan::call('config:clear') (or config:cache if your deploy pipeline caches). The package only touches the file; it has no idea Laravel exists.

Gotchas and Tips

The config cache is the #1 trap. Updating .env while bootstrap/cache/config.php exists changes nothing at runtime — env() will keep returning stale values, and config() will keep returning values baked at cache time. Always clear (or re-cache) config after a write.

env() is already resolved in the current process. Even after clearing the cache, the Dotenv instance loaded the file at boot. You cannot read back a value you just wrote via env() in the same request. Read it from the file, or spawn a fresh process.

Quoting and escaping are the sharp edges. Values containing spaces, #, =, $, quotes, or newlines are where hand-rolled dotenv writers usually fall down — and this package does not use vlucas/phpdotenv to write, so its serialization may differ from how Laravel's parser reads the file back. Test values like p@ss#word, "quoted", a=b, and $VAR before trusting it with arbitrary user input. For anything user-supplied, validate aggressively or store it somewhere other than .env.

Commented-out keys and duplicates. A line like # DB_HOST=old is not the same as DB_HOST=; matching behavior for commented lines, duplicate keys, and keys that only appear as substrings of others is the thing to verify by reading the source. If a key is missing entirely, you need to know whether the package appends it or silently no-ops — the two behaviors have very different deploy-script consequences.

Line endings and trailing newline. CRLF files, a missing final newline, and blank-line handling are classic sources of "it worked locally but mangled the file in CI." Run it once against a fixture and cat -A the result.

No locking, no atomic write. Two concurrent requests writing .env will race; you can lose a key or produce a corrupt file. Treat .env writes as a single-operator, deploy-time operation — never from a normal web request in a multi-worker setup. If you must write from a web-facing admin panel, serialize it behind a queue job with a lock and take a backup first:

copy($path, $path . '.bak-' . now()->timestamp);

Permissions. The PHP user (often www-data or forge) needs write access to .env — and granting that to a web-facing process is a real risk if the app is also writable there. Prefer deploy-time or CLI writes.

Extension point is "wrap it, don't fork it." With a surface this small, the right move is a thin application-level service that owns backups, validation, locking, and cache clearing, and delegates the actual line rewrite to the package. That keeps the upgrade path clean and makes it trivial to swap the implementation later.

Debugging recipe: write, then git diff .env (or diff .env .env.bak) — the output tells you immediately whether the package rewrote in place, appended, or normalized whitespace. Keep .env gitignored in production, but a tracked fixture .env in tests makes this diff step free.

Sanity check on scope. Five stars, zero dependents, last release March 2024 — it works, but nothing is going to catch a regression for you. Pin the version, keep a backup, and do not let it become the load-bearing component of anything important.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle