koel/dotenv-editor
A Laravel package for programmatically reading, updating, and deleting variables in .env files, with backup support and a simple API for managing environment configuration at runtime.
koel/dotenv-editor is a maintained fork of brotzka/laravel-dotenv-editor that gives you a programmatic API over the raw .env file (read keys, set/delete values, create/restore backups). It is intentionally thin: no database, no encryption, no queueing — just file manipulation with a dotenv-aware parser.
Install and confirm wiring
composer require koel/dotenv-editor
On Laravel 5.5+ the service provider and DotenvEditor facade alias are auto-discovered. If you are on an older version, register Koel\DotenvEditor\DotenvEditorServiceProvider in config/app.php and add the facade alias manually. Note that most tutorials online are for the original Brotzka\DotenvEditor namespace; the API is nearly identical, but verify imports against vendor/koel/dotenv-editor/src/ rather than copy-pasting old snippets.
Publish the config so you can point the package at the right file and backup directory:
php artisan vendor:publish --provider="Koel\DotenvEditor\DotenvEditorServiceProvider"
Review config/dotenv-editor.php (filePath, backup path/settings), then verify the web server user can write .env and the backup directory.
First use case — an admin settings screen
The canonical day-to-day use is letting an administrator update SMTP/mail credentials or a third-party API key from the UI instead of SSH-ing into the server:
use DotenvEditor;
DotenvEditor::load(); // read the current file into memory
DotenvEditor::setKeys([
['key' => 'MAIL_HOST', 'value' => 'smtp.postmarkapp.com'],
['key' => 'MAIL_USERNAME', 'value' => $username],
// ...
]);
DotenvEditor::save(); // writes the whole file back
Where to look first
vendor/koel/dotenv-editor/src/DotenvEditor.php — the entire public API is one class; skim it before trusting the README.config/dotenv-editor.php — file path and backup behavior.Start by writing a single key to a throwaway copy of .env (point filePath at a fixture) before wiring it into a controller.
Wrap it in your own service, not controllers. The package is a low-level file editor. Put it behind a class such as App\Services\EnvFileService with methods like updateMailSettings(array $data) so key names, validation, backups, and post-save cache handling live in one place.
Whitelist keys explicitly. Never pass a user-supplied key name straight into setKey(). Accept a form request, map allowed fields to a fixed array of env keys, validate each value (length, allowed characters, email/host format), then call setKeys() once.
Batch, then save once. setKeys([...]) followed by a single save() minimizes write window and diff noise. Avoid save() inside loops.
Read before write. Use keyExists() / getValue() (or parse getKeys()/getContent()) to populate form defaults and to detect no-op updates. This also lets you show "unchanged" and avoid rewriting the file unnecessarily.
Backups as your undo button. The package ships backup helpers (createBackup(), getBackups(), restoreBackup(), deleteBackup()). A safe workflow for bulk edits:
DotenvEditor::load();
$backup = DotenvEditor::createBackup();
try {
DotenvEditor::setKeys($changes);
DotenvEditor::save();
} catch (\Throwable $e) {
DotenvEditor::restoreBackup($backup);
throw $e;
}
Apply changes to the running app after saving. Writing the file does not update the current process. After a successful save, either:
Artisan::call('config:clear') and reload relevant config values manually,putenv('KEY=value') for values you need immediately,Artisan::call('queue:restart') (and octane:reload if applicable).Common pattern: update .env, clear config cache, then redirect with a message telling the admin the change is applied after workers restart.
Serialize writes with a lock. Treat .env like a shared resource:
Cache::lock('dotenv-editor', 10)->block(5, function () use ($changes) {
DotenvEditor::load();
DotenvEditor::setKeys($changes);
DotenvEditor::save();
});
Audit and mask. Log which keys changed (not their values), by whom, and when. When rendering forms, show a masked placeholder for secrets and only overwrite the key if the admin typed a new value — otherwise getValue() hands you a secret you should never echo back into HTML.
Script it from Artisan when needed. A small command (env:set KEY value --backup) is handy for deploy pipelines or support runbooks, using the same service class as the UI.
Test against a fixture. In tests, override the config filePath to a temp copy before resolving the facade, and assert on file contents. Never let tests touch the real .env.
Group keys with comments. Use addComment() / addEmpty() plus setKeys() to keep the file readable (e.g., a # Mail section); call save() afterward. This is the main reason to prefer the package over hand-rolled file_put_contents — it preserves existing lines and comments reasonably well.
.env once at bootstrap; env() reads already-populated values. After save() you must clear config cache and/or restart workers/Octane. With config:cache active, Laravel ignores .env entirely — document that admins must run php artisan config:clear after editing, or do it programmatically.#, =, $, quotes, or newlines can corrupt or silently truncate the line. Wrap values in double quotes yourself, escape " and \$ (phpdotenv interpolates $VAR inside double quotes), and reject multi-line values. Private keys and certificates do not belong in .env via this tool.KEY= yields an empty string; KEY=null yields null. Choose deliberately and be consistent with how your config reads the value.load() is not implicit forever. The facade is a singleton; if something else modified .env during the request, call load() again before reading. Likewise, getContent()/getKeys() reflect the last load, not disk.MAIL_HOTS) is happily written as a new variable. Whitelist keys in your own code and compare against .env.example in CI to catch drift..env and the backup directory. Bind mounts in Docker often produce root-owned files; check ownership and umask, and surface a clear error if is_writable() fails rather than a generic 500.storage/app/... by default. Keep them out of git, out of public disks, and prune them; a "helpful" backup feature is a credential leak if exposed. Also back up before a restoreBackup() — it overwrites the current file.APP_KEY through a UI. Changing it invalidates encrypted columns, sessions, and signed URLs. Treat it as a deploy-time operation with a migration plan, not an editable field..env. Ensure filePath points at an existing file; bootstrap it from .env.example if absent, and fail loudly otherwise..env.example, never .env.Brotzka\DotenvEditor\.... This fork uses its own namespace; mixing imports causes "class not found" errors that look like installation failures. Clear bootstrap/cache after switching.vlucas/phpdotenv + file I/O if it goes stale.getContent() after load(), write to a fixture path first, confirm the backup directory exists and is writable, and check config('dotenv-editor.filePath') actually resolves where you think.filePath and backup settings via config, extend the DotenvEditor class and rebind it in a service provider if you need custom parsing or logging, and swap the facade binding in tests to a fake.How can I help you explore Laravel packages today?