geo-sot/filament-env-editor
Filament plugin for viewing and editing your Laravel .env file straight from the admin panel. Manage environment variables through a clean, validated interface with safe saving—no SSH or manual file editing needed.
What it buys you: a Filament page that parses your .env file into a form of key/value rows and writes changes back to disk. It is an operations convenience, not a configuration system — treat it as a guarded "change these five runtime values without SSH" screen, and nothing more.
Install and wire it up
composer require geo-sot/filament-env-editor
php artisan vendor:publish --tag="filament-env-editor-config"
php artisan optimize:clear
Register the plugin on the panel(s) that should expose it — typically a back-office or admin panel, never a customer-facing one:
// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugins([
\GeoSot\FilamentEnvEditor\FilamentEnvEditorPlugin::make(),
]);
}
Where to look first, in this order
config/filament-env-editor.php — the published config is where the package's real opinion lives (which keys are grouped/hidden/labelled, and how the page is registered). Read it before touching anything else.GeoSot\FilamentEnvEditor\FilamentEnvEditorPlugin — small file; the fluent methods on it are your customization surface (navigation group/label, authorization hook, page class).src/ and resources/views/ — this is where save/parse logic lives, and it is short enough to audit fully. With ~24 stars and zero dependents you should absolutely read it rather than assume behavior.First use case to ship: a staging-only page that lets a release manager rotate third-party API keys and mail credentials (MAIL_HOST, MAIL_USERNAME, MAIL_PASSWORD, STRIPE_KEY, SENTRY_LDSN) without a deploy. Gate it to a super_admin role, exclude everything structural (APP_KEY, DB_*, CACHE_STORE, SESSION_DRIVER), and clear config cache after save. That is 90% of the package's realistic value.
Environment-scoped registration. Only mount the plugin where editing .env is sane — staging and production ops panels, not local (where devs edit the file directly) and never on a tenant panel:
->plugins(array_filter([
! app()->isLocal()
? \GeoSot\FilamentEnvEditor\FilamentEnvEditorPlugin::make()
->navigationGroup('Operations')
: null,
]))
Authorization is the whole security model. Do the check twice: once at the panel level (FilamentUser::canAccessPanel) and once on the plugin/page, so a future panel change can't silently widen access:
// app/Models/User.php
public function canAccessPanel(Panel $panel): bool
{
return $this->hasRole('super_admin') && $this->two_factor_confirmed_at !== null;
}
If the plugin exposes an access/authorize callback, bind it to a dedicated Gate (edit-env-file) rather than an inline role string — future-you will want to grant it to one contractor temporarily.
Group keys into human sections. Rather than presenting 80 raw rows, map prefixes into tabs/sections in the config (e.g. APP_* → "Application", MAIL_* → "Mail", AWS_*/S3_* → "Storage"). Use the config's label/description hooks to annotate the dangerous ones: "Changing APP_URL logs everyone out."
Mask secrets. Anything that is a credential should render as a password field with reveal disabled. Values typed into a Filament form travel through the Livewire payload — they are in the browser's network tab and in your server logs if you log requests. Minimise what's rendered in plaintext:
// pseudo-schema — confirm the exact hook in the published config/page class
TextInput::make('MAIL_PASSWORD')->password()->revealable(false)
Audit the change, not the value. Wrap or extend the save action so each edit writes an activity row containing the key names changed, the acting user, and the timestamp — never the values:
activity()
->causedBy(auth()->user())
->withProperties(['keys' => $changedKeys, 'env' => app()->environment()])
->log('env file updated');
This is the single highest-value addition you can make; without it, "who changed APP_DEBUG?" is unanswerable.
Clear caches after every save. Whatever the package does internally, verify it. Run php artisan config:clear (and optimize:clear in production, where config is cached) as part of the post-save path, plus queue:restart and octane:reload when those runtimes are in play.
Use it for drift review, not just editing. Point a read-only view at .env.example vs .env and let it flag missing/new keys — this catches the classic "deploy added a var, nobody set it" failure that this package is otherwise a band-aid for.
Keep it out of the deploy path. .env should still be written by your provisioning tooling (Envoyer, Kamal, Ansible, Kubernetes secrets). The panel is for runtime rotation between deploys; if the two ever disagree, the next deploy wins and silently reverts the panel edit. Say so in the page's helper text.
APP_KEY will detonate your app. Rotating it invalidates encrypted sessions, cookies, and any encrypted casts. It has no business being editable in a browser. Exclude it in config, and verify the exclusion by loading the page.php artisan config:cache has run, env() calls inside config files are resolved at boot; editing .env changes nothing until the cache is cleared. If a saved value "doesn't apply," that's almost always the cause — not the package.octane, and scheduler daemons read env at boot. A saved MAIL_PASSWORD won't reach the worker until queue:restart. Build this into the save flow or your on-call runbook.#, =, spaces, or newlines. PEM certificates, JSON service-account blobs, and private keys should live in files referenced by path, never inline in .env. Test round-trips with a value like P@ss#word = "x" before trusting the screen in production..env becomes a generated artefact — so keep .env.example (commented, ordered, in git) as the canonical documentation.'' makes env('KEY', 'default') return '', not the default. If you want a default to apply, the key has to be removed, not blanked. Know which behaviour the save action gives you..env. After running artisan as root, .env often ends up root-owned and saves fail with a permission error (or worse, succeed partially). There is generally no locking — a deploy writing .env while an admin saves can corrupt the file. Take a timestamped backup on every save and keep one deploy's worth of history.APP_DEBUG=true in production is an instant information-disclosure incident. Consider hard-blocking APP_DEBUG and APP_ENV from the editable set entirely, not just labelling them.canAccessPanel, the plugin's authorization hook, and 2FA. And remember Filament's own error pages / Ignition-style debug output can dump environment values on a misconfigured panel.composer.json, read the diff on upgrade, and test the page explicitly after any Filament major bump (v3 → v4 changed page/plugin registration contracts).putenv()/config() for value assertions; a test suite that writes your local .env will eventually wipe it.How can I help you explore Laravel packages today?