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

Dotenvwriter Laravel Package

mirazmac/dotenvwriter

Mirazmac DotenvWriter is a PHP package for safely writing and updating .env files programmatically. Set, change, or delete environment variables while preserving comments and formatting—ideal for installers, admin panels, and CLI tools modifying Laravel or PHP app config.

Deep Wiki
Context7

Getting Started

Install via Composer — the library has no framework requirements and no transitive dependencies beyond PHP:

composer require mirazmac/dotenvwriter

The entire public surface lives in a single class, MirazMac\DotEnvWriter\DotEnvWriter. Point it at an existing .env, mutate it, and persist:

use MirazMac\DotEnvWriter\DotEnvWriter;

$env = new DotEnvWriter(__DIR__ . '/.env');
$env->set('APP_ENV', 'staging');
$env->save();

The first-use case to reach for this package is programmatic mutation of an existing .env during install or deployment — e.g. a php artisan app:install command, a Docker entrypoint script, or an admin "Settings → Environment" screen that needs to flip APP_DEBUG, MAIL_MAILER, or QUEUE_CONNECTION without a human editing the file. Unlike vlucas/phpdotenv (which is read-only) this package is purpose-built to write back while preserving surrounding comments and key order, which is the reason to pick it over file_put_contents() string surgery.

Start by reading the class itself — it is short. Then run the composer test suite and inspect the fixtures under tests/ to see exactly how quoting, comments, and blank lines are round-tripped. That fixture set is the real API documentation.

Implementation Patterns

Deployment / provisioning scripts. Wrap writes in a small service so the CLI and web layers share behaviour:

final class EnvFile
{
    public function __construct(private string $path) {}

    public function put(array $values): void
    {
        $writer = new DotEnvWriter($this->path);
        foreach ($values as $key => $value) {
            $writer->set($key, $value);
        }
        $writer->save();
    }
}

Call it from a Symfony Console command or a Laravel Artisan command invoked by your post-deploy hook. Keep the .env path injectable so tests can point at a fixture.

Admin settings panels. This is the classic use case: a form POSTs new SMTP credentials, and the controller writes them back. Always follow the write with a cache purge, or your app will keep using stale values:

$env->set('MAIL_HOST', $request->input('mail_host'));
$env->set('MAIL_PASSWORD', $request->input('mail_password'));
$env->save();

Artisan::call('config:clear');

Batch updates. Prefer loading the file once, calling set()/delete() repeatedly, and saving once at the end. Each save() is a disk write; on a network filesystem or a Docker bind mount, that cost adds up quickly. Treat the writer as a unit-of-work object, not a per-key fire-and-forget call.

Chained, atomic-ish writes. set() and delete() are designed to chain, which pairs well with a transactional pattern:

$tmp = $envPath . '.tmp';
copy($envPath, $tmp);

$writer = new DotEnvWriter($tmp);
$writer->set('FEATURE_X', 'true')->delete('LEGACY_FLAG')->save();

rename($tmp, $envPath); // atomic on the same filesystem

This protects you against half-written files when a disk write fails mid-save, which matters because a corrupt .env will take the whole application down.

Integration with .env.example / skeleton generation. Use the writer to generate a fresh .env from a template — copy .env.example to .env, then set() the values that need to be derived at deploy time (app key, database URL, etc.). This gives you a repeatable bootstrap instead of a sed chain.

Testing. In your test suite, point the writer at a temporary copy of .env.testing and assert on file contents after save() rather than on in-memory state — the value of this package is the serialisation, so assert on the serialised output:

$path = tempnam(sys_get_temp_dir(), 'env');
copy(base_path('.env.example'), $path);
(new DotEnvWriter($path))->set('DB_DATABASE', 'testing')->save();
$this->assertStringContainsString('DB_DATABASE=testing', file_get_contents($path));

Gotchas and Tips

Permissions are the number-one failure. save() will fail (often silently returning false) if the web user cannot write to .env. In the standard Laravel layout the file is owned by your deploy user, not www-data. Do the write in a CLI/queue context where the correct user runs it, or chown the file to the FPM user. Always check the return value of save() — a false here is a production incident waiting to happen.

Concurrent writes can corrupt the file. There is no file locking in the library. Two simultaneous requests in an admin panel writing different keys will race. Guard writes with an application-level lock (Cache::lock() in Laravel, flock() elsewhere), or better, force writes through a queued job that runs single-threaded.

Laravel's config cache ignores your changes. If config:cache has been run, your .env edits have no effect until you re-cache. Any write to .env from inside a Laravel app should be immediately followed by config:clear (in local/staging) or config:cache (in production). This trips up almost everyone building a settings UI.

Quoting and escaping are the writer's responsibility, not yours — but only up to a point. The library quotes values containing spaces or special characters, but it is not a full shell escaping layer. Values with newlines, backslashes, or nested quotes are where round-tripping gets fragile. For anything user-supplied and non-trivial (certificates, JSON blobs), store it elsewhere — a database column or a separate file referenced by path — rather than stuffing it into .env.

Preserve-then-rewrite, not parse-then-rebuild. A stated feature is preservation of comments and formatting. Do not read the file with your own parser and rewrite it with this class; you will lose comments and ordering. Let the writer do the round-trip itself, and only call set()/delete() on the keys you care about.

Missing files. Verify the behaviour on a nonexistent path before shipping — depending on version, the constructor may throw or save() may create it. Safer: copy(base_path('.env.example'), $envPath) first, then instantiate.

Debug by diffing, not dumping. When a write "doesn't stick," git diff .env (if tracked) or diff against a backup is the fastest diagnostic. Keep a timestamped backup before any programmatic write in production — copy($env, $env . '.' . date('YmdHis')) — and prune old ones. It's cheap insurance and has saved more than one deployment.

Extension point is low. It's a ~200-line single-class library with no events, no interfaces, and no adapters. If you need validation, backups, locking, or cache invalidation, write a thin wrapper (like the EnvFile example above) rather than expecting the package to grow them. That wrapper is also the right place to swap in another implementation later — the underlying library is low-risk to replace if the 21-star project goes quiet, so keep the dependency confined to one class in your codebase.

Don't use it to author secrets in version control. It writes to disk in plaintext, same as any .env. It is a convenience for local/dev and small production boxes — if you have a secrets manager, the writer is the wrong tool.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels