aranyasen/laravel-env-sync
A Laravel Artisan package for keeping .env files in sync across environments. Add missing keys from .env.example, compare and merge configuration values, and avoid manually copying variables during deployments or onboarding new developers.
Install it as a dev dependency — this is a developer-machine/CI tool, not something your production app needs at runtime:
composer require --dev aranyasen/laravel-env-sync
The current release line is v3.x (v3.0.1 at time of writing), which supports PHP up to 8.5. That means the package is tracking modern PHP versions, but it also means if your app is still on an older PHP/Laravel combination, check composer.json for the actual constraint floor before blaming your own setup.
The service provider should be auto-discovered. Immediately after install, do two things:
php artisan list | grep -i env
php artisan <command> --help
php artisan vendor:publish --provider="Aranyasen\EnvSync\EnvSyncServiceProvider"
First use case: you pull main, a teammate added STRIPE_WEBHOOK_SECRET to .env.example, and your local app now throws a confusing Missing required parameter error at runtime. Instead of diffing two files by hand, run the sync command and let it append the missing keys. Before you trust it, back up first:
cp .env .env.bak
php artisan env:sync # or whatever the actual command is — check `list`
git diff --no-index .env.bak .env
If the diff looks correct (appended keys only, existing values untouched), delete .env.bak and make this a habit. If it rewrites or reorders your file, stop and read the config before going further.
Treat .env.example as the contract, not .env. The workflow that makes this package pay off is a team rule: any PR that adds env('FOO') must add FOO= to .env.example. The sync tool then becomes the mechanism that propagates that contract, not a substitute for it.
Onboarding / post-pull loop. Add a Composer script so the step is muscle memory:
"scripts": {
"post-pull": ["@php artisan env:sync"]
}
Or a simple shell alias: alias esync='php artisan env:sync && php artisan config:clear'.
CI parity gate. The highest-value use is catching drift in CI rather than on developer machines. In a pipeline job that has an .env built from secrets:
- run: cp .env.example .env # baseline
- run: php artisan <sync-command> --check # if a check/dry-run flag exists
If the package has no non-mutating mode, don't fight it — write the assertion yourself. A 20-line test is more reliable than a 4-star dependency:
it('keeps .env.example in sync with the app', function () {
$example = collect(file(base_path('.env.example'), FILE_IGNORE_NEW_LINES))
->filter(fn ($l) => $l && !str_starts_with(trim($l), '#'))
->map(fn ($l) => explode('=', $l, 2)[0])
->filter();
$missing = $example->diff(
collect(file(base_path('.env'), FILE_IGNORE_NEW_LINES))
->map(fn ($l) => explode('=', $l, 2)[0])
);
expect($missing)->toBeEmpty("Missing from .env: {$missing->implode(', ')}");
});
Deploy hooks — think twice. The sync command writes files. On a read-only or container filesystem (or where .env is injected by the platform), this will either fail or create a stale file that shadows real environment variables. Gate it behind an environment check, and generally prefer php artisan config:cache with platform-injected env vars in production.
Multiple env files. Projects usually have .env, .env.testing, .env.ci. If the package only targets .env (likely), wrap it in a small script that syncs each file, or accept that .env.testing drift is caught by your test suite instead.
.env. The package's whole value proposition is editing .env; make absolutely sure nothing in your tooling stages it. Add .env and .env.* (except .env.example) to .gitignore and verify git status is clean before and after running the command..env is safe; the reverse (deleting keys from .env that aren't in the example) silently destroys local secrets and debugging overrides. Check the config for a "remove stale keys" option and leave it off unless you have a very disciplined team.FOO= is present-but-empty, which for Laravel's env() means '', not null. Sync tools frequently conflate these; if a key keeps reappearing after you "add" it, that's why.#, spaces, quotes, or multi-line PEM blocks (very common with *_KEY secrets) are where hand-rolled .env parsers break. Test with a real-world .env before rollout, and keep a backup the first few runs.${VAR} interpolation and ordering. If your .env uses variable references, reordering or appending can change resolution. Prefer append-only behavior.php artisan config:clear if you've ever run config:cache locally. Most "the sync didn't work" reports are this..env can still be overridden by an exported shell variable or Docker --env-file. When debugging, run php artisan tinker --execute="dump(env('FOO'));" and remember precedence.core.autocrlf quirks, appended lines may mix CRLF/LF. Set .gitattributes (*.env* text eol=lf) to avoid noisy diffs."aranyasen/laravel-env-sync": "^3.0" (or an exact tag if you want zero surprises), read the source (it's small) before wiring it into CI, and keep a manual fallback documented in your README: diff <(cut -d= -f1 .env.example | sort) <(cut -d= -f1 .env | sort). Extension points beyond the config file are unlikely to be stable, so avoid subclassing internals.illuminate/support constraints. The package supports PHP 8.5, but that says nothing about which Laravel majors are allowed — check the package's composer.json for the framework range before debugging your own setup.How can I help you explore Laravel packages today?