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

Laravel Dotenv Editor Laravel Package

brotzka/laravel-dotenv-editor

Laravel Dotenv Editor lets you read, update, and manage your .env file directly from your application, making it easy to edit environment variables from an admin panel without server access. Clean, fluent API for getting, setting, deleting, and saving keys and values. Simple to install.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config:

composer require brotzka/laravel-dotenv-editor
php artisan vendor:publish --provider="Brotzka\DotenvEditor\DotenvEditorServiceProvider"

This drops config/dotenv-editor.php into your app, which controls the default .env path, the backup directory, and whether backups are written automatically before every save. The facade DotenvEditor is auto-registered; there is no need to add it to config/app.php on modern Laravel.

Your first use case is almost always an admin-facing settings screen — letting a site owner rotate an SMTP password, swap a Stripe key, or toggle a feature flag without SSH access. The minimal flow is load → read → mutate → save:

use Brotzka\DotenvEditor\DotenvEditor;

// Load the project's default .env (path comes from config)
DotenvEditor::load();

if (DotenvEditor::keyExists('MAIL_PASSWORD')) {
    DotenvEditor::changeKey('MAIL_PASSWORD', 'new-secret');
} else {
    DotenvEditor::addKey('MAIL_PASSWORD', 'new-secret');
}

DotenvEditor::save();

Look first at config/dotenv-editor.php (paths and backup behavior) and then at the facade's getKey, changeKey, addKey, deleteKey, save, backup, restore, and getBackups methods — those cover nearly everything the package does.

Implementation Patterns

Wrap it in a service, don't call the facade from controllers. The facade holds a mutable buffer that is shared across the request lifetime. A thin service makes it explicit and testable:

class EnvSettingsService
{
    public function get(string $key): ?string
    {
        DotenvEditor::load();
        return DotenvEditor::getKey($key);
    }

    public function put(array $values): void
    {
        DotenvEditor::load();
        DotenvEditor::backup();

        foreach ($values as $key => $value) {
            DotenvEditor::keyExists($key)
                ? DotenvEditor::changeKey($key, $value)
                : DotenvEditor::addKey($key, $value);
        }

        DotenvEditor::save();
    }
}

Rotate a single key with a validation-first controller. Treat the .env like any other stored secret store — validate, authorize, then write:

public function updateSmtp(UpdateSmtpRequest $request, EnvSettingsService $env)
{
    $env->put($request->validated());   // only the whitelisted keys

    // Sync the running process so the new values take effect immediately
    foreach ($request->validated() as $key => $value) {
        config([strtolower($key) => $value]);
    }

    return back()->with('status', 'Saved');
}

Whitelist keys explicitly. Never loop over $request->all() — an attacker posting APP_KEY or DB_PASSWORD should be a validation failure, not a working exploit. A Rule::in(['MAIL_HOST','MAIL_PORT',...]) on the key, or a fixed form request, is the right pattern.

Prefer changeKey then addKey over blind setKey when you care about auditability. changeKey fails loudly if the key is missing, which surfaces typos and renamed keys instead of silently appending duplicates.

Manage backups deliberately. Use DotenvEditor::backup() before bulk edits, and DotenvEditor::restore($backup) in an admin "rollback" action. Enumerate with getBackups() for a listing page. In production, schedule a cleanup or point backupPath at a directory with retention handling — otherwise you accumulate one file per save indefinitely.

Test with a fixture .env. DotenvEditor::load(base_path('tests/fixtures/.env.testing')) lets you exercise reads/writes against a temp copy without touching the real file. Assert both that the key changed and that unrelated comments and ordering survived.

Multi-file / multi-tenant. load($absolutePath) accepts a path, so a per-tenant .env under storage/tenants/{id}/.env works fine — just run load() before every operation, because the editor retains state from the previous load.

Gotchas and Tips

Editing .env does not change env() in the current process. This is the number one trap. Laravel reads .env once at boot into the config repository. After save(), env('MAIL_PASSWORD') still returns the old value. You must also update config() for the running request, and other requests only pick up the change after config cache is cleared (or, if you're not cached, on their next boot). Communicate that to whoever clicks "Save" — a "restart queue workers" note next to the button saves a lot of confusion.

config:cache makes the whole package moot. If bootstrap/cache/config.php exists, the framework never touches .env again. Either disallow edits in cached environments, or have your save action run php artisan config:cache again (careful with permissions and timing under load).

Quoting and escaping are the sharpest edge. Values containing spaces, #, =, quotes, or $ will break parsing or be silently truncated. Always wrap written values in double quotes yourself, escape embedded double quotes and backslashes, and refuse to write values with newlines. Booleans become strings — "true" vs true matters to env()'s FILTER_VALIDATE_BOOLEAN, and null/(null) handling is inconsistent across Laravel versions.

File permissions will bite you. The web server user (e.g. www-data) needs write access to .env and the backup directory. chmod 664 plus group ownership is the usual fix; chmod 777 is the usual mistake. Similarly, storage/-based backup paths need the same treatment.

No concurrency control. Two simultaneous saves can interleave and corrupt the file. For anything beyond an occasional admin edit, wrap writes in a Cache::lock('dotenv-write', 10) and re-load() inside the lock.

Package staleness is a real risk. The last release was January 2023. Verify it against your Laravel version — Laravel 11/12 support is not guaranteed by the maintainer. If you're on a newer framework, budget time to check the facade bindings and test the write path before building a feature on top of it.

Backups live in the app directory by default, not in storage/. If your deploy pipeline does a clean git pull-style checkout, confirm the configured backupPath survives deployment — otherwise your rollback history vanishes on every release.

Extension points are thin by design. There's no event fired on save and no pluggable parser. If you need change auditing, fire your own EnvUpdated event and log the keys (never the values) from inside your service wrapper. That's also the correct place to hang a "notify ops" listener.

Debugging tip: when a write looks like it succeeded but nothing changed, dump DotenvEditor::getContent() after mutating and before save(). That distinguishes "my buffer was never updated" (missing load() or wrong file) from "the write failed" (permissions or path).

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin