lionix/envclient
Laravel package for working with .env files programmatically: read, update, and manage environment variables from your app or an admin panel, so configuration changes can be made without manual file editing or redeploys.
Install the package with Composer:
composer require lionix/envclient
Laravel’s package discovery should register the service provider automatically. On older Laravel versions, add the provider manually in config/app.php. Publish the package config so you can see what it controls:
php artisan vendor:publish --provider="Lionix\EnvClient\EnvClientServiceProvider"
Start by opening the published config (the 2.2.0 notes reference config/env.php, including the hidden patterns used for secret masking) and the package README. The config controls the .env file location, secret masking, the backup option, and validate_on_boot behaviour. Then discover the exact command signatures — this release added several:
php artisan list | grep -i env
As of 2.2.0 the command surface is roughly env:get, env:set, env:unset, env:list, env:diff, env:sync, env:restore, env:generate, all accepting a --file option (plus --encrypted, --key, --cipher for encrypted files). Confirm the exact flags from php artisan list before scripting them.
The best first use case is now a drift check plus a deployment guard. Wire env:diff into CI so a feature branch that adds a config key fails until .env.example is updated, then validate before caching:
php artisan env:diff # non-zero exit if env file and .env.example disagree
php artisan env:get APP_KEY --reveal > /dev/null
php artisan config:cache
That combination catches the two most common production failures: an undocumented new variable and a missing or malformed value that only surfaces when the app boots.
Use the fluent client behind a small application service. Rather than sprinkling the package facade through controllers, wrap it — including the new forget():
namespace App\Support;
use Lionix\EnvClient\Facades\EnvClient;
class Environment
{
public function get(string $key, mixed $default = null): mixed
{
return EnvClient::get($key, $default);
}
public function set(string $key, string $value): void
{
EnvClient::set($key, $value);
}
public function forget(string $key): void
{
EnvClient::forget($key); // EnvForgetterInterface, implemented by EnvSetter
}
public function has(string $key): bool
{
return EnvClient::has($key);
}
}
This isolates the package API, makes testing easier, and gives you one place to add logging, key allow-lists, or backup handling.
Keep env:diff in CI and env:sync in provisioning. They solve different problems. env:diff is a read-only gate:
- run: php artisan env:diff
env:sync writes missing keys from .env.example into the target file, which is useful when standing up a fresh environment — but it writes placeholder values, so treat it as a scaffolding step, not a repair step for a live production .env. Review its semantics on a throwaway file first.
Turn on backup before any scripted writes. The backup option copies the file to <file>.backup before every change, and env:restore puts it back. Enable it in config for any environment where you script env:set/env:unset:
# .env or .env.<environment>
ENVCLIENT_BACKUP=true
Combined with the "no rewrite when nothing changes" behaviour, this makes bulk provisioning idempotent and recoverable.
Use --file for multi-file workflows instead of editing .env. Instead of swapping files in and out of the project root, target them explicitly:
php artisan env:set DB_HOST="$DB_HOST" --file=.env.staging
php artisan env:diff --file=.env.staging
This also covers the case where the config is cached and .env isn’t loaded — with --file, values are read from the file itself rather than the runtime environment.
Keep encrypted files in the repo, not the plaintext. With --encrypted, --key and --cipher you can read and update a file produced by Laravel’s env:encrypt without writing decrypted contents next to the application:
php artisan env:get APP_KEY --file=.env.production.encrypted --encrypted --reveal
php artisan env:set CACHE_STORE=redis --file=.env.production.encrypted --encrypted
Supply the key from your CI secret store (or LARAVEL_ENV_ENCRYPTION_KEY), never from a committed file. Note illuminate/encryption is now a direct dependency, so it will be present, but the key handling is still your responsibility.
Use validate_on_boot deliberately. The new option (log or exception) validates the environment on every web request and throws InvalidEnvironmentException. exception is a reasonable default for staging; in production, prefer a startup check plus log, and register a handler so the exception degrades gracefully:
// bootstrap/app.php (Laravel 11+) or app/Exceptions/Handler.php
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (InvalidEnvironmentException $e) {
report($e);
return response('Service misconfigured', 503);
});
})
Compose the ready-made rule sets. AppRules, DatabaseRules, MailRules, QueueRules, RedisRules and AwsRules cover the standard keys; use them in a scheduled health command and layer your own application-specific keys on top:
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
class ValidateEnvironment extends Command
{
protected $signature = 'app:validate-env';
public function handle(): int
{
$validator = Validator::make($this->currentEnv(), [
...DatabaseRules::rules(),
...MailRules::rules(),
'FEATURE_FLAG_X' => ['required', 'boolean'],
]);
if ($validator->fails()) {
foreach ($validator->errors()->all() as $error) {
$this->error($error); // key names and status only — never values
}
return self::FAILURE;
}
return self::SUCCESS;
}
}
Read the file directly (via the client or --file) rather than config(), otherwise a cached production environment validates the cached values, not the current .env.
Subscribe to EnvironmentFileUpdated and EnvironmentVariablesGenerated for audit trails. These are the natural hook for "who changed what, when" — log key names only, and ship them to your audit channel rather than the application log:
Event::listen(EnvironmentFileUpdated::class, function ($event) {
AuditLog::record('env.updated', ['keys' => $event->keys /* names only */]);
});
Test against a fixture file, and assert on masking. Point the package at a temporary env file via the --file option or config override in your test suite, and never let tests read or write the real project .env. Add a test that env:get output does not contain a hidden value and that --reveal does — that behaviour is now a security boundary.
Use it for diagnostics, not as a config source. The package reads and writes the env file; your application should still consume configuration through config(). Use the client to inspect and manage the file, and use Laravel config for runtime behaviour.
env:get now masks secrets by default — this is the breaking change in 2.2.0. Any script that pipes env:get into another tool must pass --reveal. Audit your deploy scripts before upgrading.--reveal is a secret-exfiltration footgun. It prints plaintext to stdout, which lands in CI logs, terminal scrollback, and shell history. Use it only where you immediately consume the value, and never inside a set -x script.env:set, env() returns the new value for the rest of the request or command. config() does not — and with config:cache active, the change is invisible until you re-cache. Always write values before php artisan config:cache, or re-run it afterwards.validate_on_boot runs on every request. With exception it also means a bad .env takes the whole site down rather than one deploy step. Budget for the filesystem read, prefer validating once at boot/deploy, and treat per-request exception mode as a staging-only posture..backup files contain secrets. Add *.backup (and any --file variants) to .gitignore, keep the same restrictive permissions as .env, and prune them — env:restore only knows about the most recent one.env:sync can overwrite or invent values. Confirm whether it fills only missing keys or replaces differing ones, and whether it writes example placeholders. Run it against a copy first; a silent placeholder is worse than a missing key.--key on the command line leaks via process listings and shell history. Prefer an environment variable or CI secret injection, and confirm the --cipher matches whatever env:encrypt used.#, =, quotes, $, and multiline values can break naive .env writers. Test round-trips with realistic secrets and connection strings before trusting bulk writes..env edits as structured data, and keep .env.example or a secret manager as the source of truth.env(). The bundled rule sets are opinionated — review them against your own key names before adopting.env:get/env:generate output, but your own listeners and error messages don’t get that for free.backup option, copy the file first or implement a write-to-temp-then-rename wrapper so a failed deployment cannot leave a truncated .env.How can I help you explore Laravel packages today?