PHP

Laravel

Load Envless variables in Laravel with envless/env: one guarded line in bootstrap/app.php, so env(), config() and your code read Envless values, with notes for config:cache, Octane, queues, Blade and tests.

Laravel reads its configuration through env(), and env() reads the process environment. Load Envless before Laravel boots and every env() call, every config() value and every line of your own code sees the Envless values.

bash
composer require envless/env

Load in bootstrap/app.php

php
<?php

use Envless\Env\Env;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

if (Env::isConfigured()) {
    Env::load();
}

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
    })
    ->withExceptions(function (Exceptions $exceptions): void {
    })->create();

bootstrap/app.php runs for every web request, every artisan command and every queue worker, so this one line covers all of them. Env::isConfigured() keeps it out of the way where there are no credentials: composer install runs php artisan package:discover, and a build without ENVLESS_TOKEN then boots from .env as before instead of failing.

.env and Envless together

Laravel loads .env after bootstrap/app.php and never overwrites a variable that is already set. Envless loads first, so for a name both define, Envless wins. Anything only in .env is still read from it. That makes the move gradual: put a variable in Envless, and the copy in .env stops mattering. Delete it once you trust the move.

The credentials themselves cannot come from .env, because Laravel has not read it yet when bootstrap/app.php runs. Set ENVLESS_TOKEN, ENVLESS_KEY, ENVLESS_PROJECT and ENVLESS_ENV in the real environment of your server, container or FPM pool, or commit a .envless file for the project and environment.

Reading values

php
use Envless\Env\Env;

$port = Env::number('PORT');
$checkout = Env::boolean('CHECKOUT_ENABLED');

Your config/*.php files keep calling env() as they do today. In your own code, Env::string() and its siblings are typed and throw on a missing name, where env() would return null. Generate a class with a method per variable with vendor/bin/envless-env types, --output app/Support/EnvlessEnv.php --namespace 'App\Support'.

config:cache

php artisan config:cache writes the resolved configuration, Envless values included, to bootstrap/cache/config.php in plaintext, exactly as it does with .env values. Once the configuration is cached, Laravel stops reading env() for it, so a change in Envless only reaches config() after you cache again. Run config:cache where writing values to disk is acceptable, as part of the deploy, or skip it: a load from the warm cache costs well under a millisecond per request with ENVLESS_KEY.

Octane, queues and Horizon

Under Laravel Octane, with Swoole, RoadRunner or FrankenPHP, bootstrap/app.php runs once per worker, so each worker loads once and serves every later request from memory. php artisan octane:reload starts fresh workers, which load again. Queue workers and Horizon load once per process at boot, and php artisan queue:restart makes them load again.

Client variables in Blade and Inertia

blade
<script>
    window.env = @json(Envless\Env\Env::client());
</script>
php
use Envless\Env\Env;
use Inertia\Inertia;

Inertia::share('env', fn () => Env::client()->snapshot());

Only the variables marked client in Envless are in Env::client(). A server variable is not in the payload and throws ServerOnlyException if a template reads it by name.

Tests

Env::override() replaces values for the length of a test without an API, before or after a load:

php
use Envless\Env\Env;
use Envless\Env\Override;
use Tests\TestCase;

final class CheckoutTest extends TestCase
{
    private Override $override;

    protected function setUp(): void
    {
        parent::setUp();
        $this->override = Env::override(['CHECKOUT_ENABLED' => 'true']);
    }

    protected function tearDown(): void
    {
        $this->override->restore();
        parent::tearDown();
    }
}