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 Env Editor Laravel Package

geo-sot/laravel-env-editor

A Laravel package that adds a secure web interface for viewing and editing your .env file directly from the browser, so you can manage environment variables and app config without touching the filesystem or redeploying.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require geo-sot/laravel-env-editor
php artisan vendor:publish --provider="GeoSot\EnvEditor\EnvEditorServiceProvider"

The --provider form always works regardless of the tag name, which matters here because the tag naming has changed across releases. The published config/env-editor.php is where the env file path, backup directory, and backup retention live — read it before writing any code, since almost every "why didn't my backup appear" question is answered by a path in that file.

Where to look first, in order

  1. config/env-editor.php — file path + backup path + retention count.
  2. The facade (GeoSot\EnvEditor\Facades\EnvEditor) and the class it points at in src/ — the docblocks are the real documentation. This package has a thin README and no dependents, so the source is the spec. Confirm the exact signatures of getEnvFileContent(), save(), backup(), getBackups(), restore(), deleteBackup(), upload(), and download() before you build against them.
  3. The storage/ path (or configured backup dir) — make sure your web user and your CLI user can both write there.

First use case: a read → mutate → save round trip, from tinker

use GeoSot\EnvEditor\Facades\EnvEditor;

$content = EnvEditor::getEnvFileContent();   // parsed entries + file metadata
EnvEditor::backup();                          // snapshot first, always
// mutate the same array you were given, don't hand-build a new one
EnvEditor::save($content);

Do this in php artisan tinker against a throwaway .env before you put it behind a form. The single most common failure with this package is hand-crafting the array passed to save() and discovering the shape doesn't match what getEnvFileContent() returns — the round-trip is the contract.

First real integration: a read-only "environment" panel (show current values, masked) plus a "download backup" button. That gets you the plumbing — facade, backup listing, file permissions — with zero risk of corrupting .env. Add writes later.

Implementation Patterns

Wrap the package, don't scatter it. With 84 stars, 0 dependents, and a low opportunity score, treat this as a dependency you may have to replace. One thin service — App\Services\EnvironmentFile — that exposes snapshot(), all(), put(string $key, string $value), restore(string $name) — means swapping the package later is one file, not a grep.

Backup-on-write, restore-on-failure (deploy hook). The best use of this package isn't a UI, it's a safety net around deploys:

// in a deploy script / artisan command, before config:cache
$backup = EnvEditor::backup();

try {
    EnvEditor::save($mutated);
    Artisan::call('config:cache');
} catch (\Throwable $e) {
    EnvEditor::restore($backup);
    throw $e;
}

Because backups are just files, this works even if the package's API changes — you can always fall back to File::copy().

Diff-then-apply for admin forms. Never let a form post the whole env back. Render the current keys, let the user change two of them, and compute the delta server-side, then apply only changed keys onto the array from getEnvFileContent(). This preserves comments, ordering, and anything you don't understand (custom multi-line values, keys you don't recognize) — the stuff that gets destroyed by a naive full rewrite.

Allow-list the editable keys. config('env-editor.editable', ['MAIL_*', 'FEATURE_*'])-style allow-listing, enforced in your wrapper. The package will happily let you write APP_KEY, APP_DEBUG, DB_HOST, or SESSION_DRIVER, which is equivalent to giving the user a shell.

Pair with a cache-busting step, always. Editing .env changes nothing in a booted app. After save(), either call Artisan::call('config:clear') (local/dev) or enqueue a job that runs config:cache && queue:restart (production). In an Octane/queue/Horizon setup, workers hold the old values in memory indefinitely — queue:restart is not optional.

CI/local seeding. A env:seed-from-example command that copies .env.example → .env and fills placeholders is a genuinely nice use of the package, and it runs in an environment where a mistake costs nothing.

Testing. Point the config at a fixture file inside tests/ (config(['env-editor.…' => base_path('tests/fixtures/.env')])) and assert on the resulting file contents with File::get(). Never let a test suite touch the repo's real .env; a single rm in a tearDown will ruin someone's afternoon.

Gotchas and Tips

.env is read once, at bootstrap. This is the gotcha that generates every bug report. env('FOO') will not change after EnvEditor::save(), and config('foo') certainly won't. If bootstrap/cache/config.php exists, .env is not read at all — your editor silently writes to a file the app ignores. Detect this (app()->configurationIsCached()) and refuse to pretend the save took effect.

env() outside config/ returns null once config is cached. If your feature reads env('X') directly, it will appear broken in production while working locally. Route everything through config().

File ownership beats file permissions. php-fpm runs as www-data; deploys run as deploy. Writing .env from a web request usually fails or, worse, creates a root-owned backup the next deploy can't overwrite. Prefer doing writes in a queued job / artisan command running as the deploy user, and check is_writable() before you promise the user a save.

Backups are plaintext secrets. They land in storage/ — make sure that path is not under storage/app/public, is gitignored, and is excluded from any "download storage" tooling. Also check the retention setting; an unbounded backup dir eventually contains every secret the app has ever had.

Writing is not atomic. If the process dies mid-write you get a truncated .env, which is a total outage. Snapshot first (the package's backup() exists for exactly this), and if you need stronger guarantees, write the new content yourself to *.tmp and rename() it into place.

Concurrency. Two admins saving at once = lost update, possibly a mangled file. Take a Cache::lock('env-editor') around read-modify-write.

Quoting and special characters. Values with spaces, #, ", $, or newlines need quoting/escaping per dotenv rules; the parser may not round-trip multi-line values (PEM keys, JSON blobs) cleanly. Validate on input: reject newlines in values unless you've confirmed the writer handles them.

Comments and formatting may not survive. Assume any save() normalizes the file. Never edit .env you can't recreate — keep .env.example current and diff after every write in staging.

Never rotate APP_KEY through this. Changing it invalidates every encrypted column and every session cookie. Editing DB_* mid-request leaves stale pooled connections; editing mail settings leaves stale transports in long-lived workers.

Security envelope. If you expose any route: auth + a gate + CSRF + an IP allow-list, and hard-disable outside local/staging. download() and upload() are the sharpest edges — an unvalidated upload is arbitrary config injection, which is arbitrary code execution via APP_KEY/DB_*. Reject uploads that don't parse as a dotenv file with an expected key set.

No UI out of the box. Recent versions are headless (the 1.x web UI is gone). Budget for building the screen yourself; the package gives you file I/O and backups, not a settings page.

Debugging checklist when a save "doesn't work": (1) is config cached? (2) is the configured path the path Laravel actually loads? (3) is the file writable by this user? (4) did the value get re-quoted/escaped? (5) is a long-lived worker still holding the old value?

Extension points are thin by design. The config path/backup settings and your own wrapper are the extension surface. Don't subclass the internals — the class is a small file-helper, and its API has moved between minor versions.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource