KeyEnvKeyEnv
SDKs & Integrations

Laravel Integration

Use KeyEnv with Laravel applications for secure secrets management.

Laravel Integration

Integrate KeyEnv with your Laravel application to securely manage secrets like database credentials, API keys, and encryption keys.

Features

  • Load secrets before Laravel initializes
  • Environment-aware configuration (local, staging, production)
  • Service Provider with auto-discovery
  • Facade for convenient access (KeyEnv::get('key'))
  • Artisan commands for local development
  • Works with Laravel's .env file pattern
  • Supports Laravel 9.x, 10.x, and 11.x

Installation

composer require keyenv/keyenv

Quick Start

The simplest approach loads secrets in your AppServiceProvider:

// app/Providers/AppServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use KeyEnv\KeyEnv;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Load secrets before config is used
        if ($token = env('KEYENV_TOKEN')) {
            $client = KeyEnv::create($token);
            $client->loadEnv(
                env('KEYENV_PROJECT'),
                env('APP_ENV', 'local')
            );
        }
    }

    public function boot(): void
    {
        //
    }
}

Service Provider Setup

For a more robust setup, create a dedicated KeyEnv service provider:

Create the Service Provider

php artisan make:provider KeyEnvServiceProvider
// app/Providers/KeyEnvServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use KeyEnv\KeyEnv;

class KeyEnvServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Register the KeyEnv client as a singleton
        $this->app->singleton(KeyEnv::class, function () {
            $token = env('KEYENV_TOKEN');
            if (!$token) {
                return null;
            }
            return KeyEnv::create($token);
        });

        // Load secrets into environment
        $this->loadSecrets();
    }

    protected function loadSecrets(): void
    {
        $token = env('KEYENV_TOKEN');
        $projectId = env('KEYENV_PROJECT');

        if (!$token || !$projectId) {
            return;
        }

        $client = KeyEnv::create($token);
        $environment = $this->mapLaravelEnv(env('APP_ENV', 'local'));

        $client->loadEnv($projectId, $environment);
    }

    protected function mapLaravelEnv(string $laravelEnv): string
    {
        // Map Laravel environments to KeyEnv environments
        return match ($laravelEnv) {
            'local' => 'development',
            'testing' => 'development',
            'staging' => 'staging',
            'production', 'prod' => 'production',
            default => $laravelEnv,
        };
    }

    public function boot(): void
    {
        //
    }
}

Register the Provider

Add to bootstrap/providers.php (Laravel 11):

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\KeyEnvServiceProvider::class, // Add this line
];

Or in config/app.php (Laravel 9/10):

'providers' => ServiceProvider::defaultProviders()->merge([
    App\Providers\KeyEnvServiceProvider::class,
])->toArray(),

Config File Setup

Create a config file for KeyEnv settings:

// config/keyenv.php
return [
    /*
    |--------------------------------------------------------------------------
    | KeyEnv Service Token
    |--------------------------------------------------------------------------
    |
    | Your KeyEnv service token for API authentication.
    | Generate one at: https://app.keyenv.dev/settings/tokens
    |
    */
    'token' => env('KEYENV_TOKEN'),

    /*
    |--------------------------------------------------------------------------
    | KeyEnv Project ID
    |--------------------------------------------------------------------------
    |
    | The ID of your KeyEnv project.
    |
    */
    'project_id' => env('KEYENV_PROJECT'),

    /*
    |--------------------------------------------------------------------------
    | Environment Mapping
    |--------------------------------------------------------------------------
    |
    | Map Laravel environment names to KeyEnv environment names.
    |
    */
    'environments' => [
        'local' => 'development',
        'testing' => 'development',
        'staging' => 'staging',
        'production' => 'production',
    ],

    /*
    |--------------------------------------------------------------------------
    | Enabled
    |--------------------------------------------------------------------------
    |
    | Enable or disable KeyEnv secret loading. Useful for disabling
    | in local development when using .env files.
    |
    */
    'enabled' => env('KEYENV_ENABLED', true),

    /*
    |--------------------------------------------------------------------------
    | Request Timeout
    |--------------------------------------------------------------------------
    |
    | Timeout in seconds for API requests.
    |
    */
    'timeout' => env('KEYENV_TIMEOUT', 30),
];

Update the service provider to use the config:

// app/Providers/KeyEnvServiceProvider.php
protected function loadSecrets(): void
{
    if (!config('keyenv.enabled')) {
        return;
    }

    $token = config('keyenv.token');
    $projectId = config('keyenv.project_id');

    if (!$token || !$projectId) {
        return;
    }

    $client = KeyEnv::create($token, config('keyenv.timeout', 30));

    $laravelEnv = app()->environment();
    $keyenvEnv = config("keyenv.environments.{$laravelEnv}", $laravelEnv);

    $client->loadEnv($projectId, $keyenvEnv);
}

Facade Usage

Create a Facade for convenient access to KeyEnv:

// app/Facades/KeyEnv.php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * @method static array exportSecrets(string $projectId, string $environment)
 * @method static array exportSecretsAsArray(string $projectId, string $environment)
 * @method static \KeyEnv\Types\SecretWithValue getSecret(string $projectId, string $environment, string $key)
 * @method static int loadEnv(string $projectId, string $environment)
 *
 * @see \KeyEnv\KeyEnv
 */
class KeyEnv extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \KeyEnv\KeyEnv::class;
    }
}

Register an alias in config/app.php:

'aliases' => Facade::defaultAliases()->merge([
    'KeyEnv' => App\Facades\KeyEnv::class,
])->toArray(),

Now you can use the Facade:

use App\Facades\KeyEnv;

// Get a single secret
$secret = KeyEnv::getSecret(
    config('keyenv.project_id'),
    'production',
    'STRIPE_SECRET_KEY'
);
echo $secret->value;

// Get all secrets as array
$secrets = KeyEnv::exportSecretsAsArray(
    config('keyenv.project_id'),
    'production'
);

Environment-Aware Configuration

Automatic Environment Detection

// app/Providers/KeyEnvServiceProvider.php
protected function mapLaravelEnv(string $laravelEnv): string
{
    // Check for explicit KeyEnv environment override
    if ($override = env('KEYENV_ENV')) {
        return $override;
    }

    // Check for deployment platform indicators
    if (env('VAPOR_SSM_PATH')) {
        return 'production'; // AWS Lambda via Vapor
    }
    if (env('FORGE_SERVER_ID')) {
        return 'production'; // Laravel Forge
    }
    if (env('RENDER')) {
        return 'production'; // Render
    }

    // Map standard Laravel environments
    return match ($laravelEnv) {
        'local', 'development' => 'development',
        'testing' => 'development',
        'staging' => 'staging',
        'production', 'prod' => 'production',
        default => $laravelEnv,
    };
}

Per-Environment .env Files

Laravel supports environment-specific .env files. You can use different KeyEnv configurations:

# .env.local (development)
KEYENV_TOKEN=env_dev_...
KEYENV_PROJECT=your-project-id
KEYENV_ENV=development

# .env.production
KEYENV_TOKEN=env_prod_...
KEYENV_PROJECT=your-project-id
KEYENV_ENV=production

Using with .env Files

KeyEnv complements Laravel's .env pattern. Secrets loaded via loadEnv() are available through env() and config():

Bridge Pattern

Load KeyEnv secrets, then use them in your Laravel config:

// config/database.php
return [
    'default' => env('DB_CONNECTION', 'pgsql'),

    'connections' => [
        'pgsql' => [
            'driver' => 'pgsql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '5432'),
            'database' => env('DB_DATABASE', 'laravel'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
            // These values come from KeyEnv via loadEnv()
        ],
    ],
];
// config/mail.php
return [
    'default' => env('MAIL_MAILER', 'smtp'),

    'mailers' => [
        'smtp' => [
            'transport' => 'smtp',
            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
            'port' => env('MAIL_PORT', 587),
            'encryption' => env('MAIL_ENCRYPTION', 'tls'),
            'username' => env('MAIL_USERNAME'),
            'password' => env('MAIL_PASSWORD'),
        ],
    ],
];

Generate .env from KeyEnv

Use the KeyEnv CLI to generate a local .env file:

# Pull secrets to .env file
keyenv pull --env development --output .env

# Or use the PHP SDK
php -r "
require 'vendor/autoload.php';
\$client = KeyEnv\KeyEnv::create(getenv('KEYENV_TOKEN'));
echo \$client->generateEnvFile(getenv('KEYENV_PROJECT'), 'development');
" > .env

Artisan Commands

Create custom Artisan commands for KeyEnv operations:

keyenv:pull Command

// app/Console/Commands/KeyEnvPull.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use KeyEnv\KeyEnv;

class KeyEnvPull extends Command
{
    protected $signature = 'keyenv:pull
        {--env= : KeyEnv environment (defaults to current APP_ENV)}
        {--output=.env : Output file path}';

    protected $description = 'Pull secrets from KeyEnv and write to .env file';

    public function handle(): int
    {
        $token = config('keyenv.token');
        $projectId = config('keyenv.project_id');

        if (!$token || !$projectId) {
            $this->error('KEYENV_TOKEN and KEYENV_PROJECT must be set');
            return 1;
        }

        $environment = $this->option('env')
            ?? config('keyenv.environments.' . app()->environment())
            ?? app()->environment();

        $output = $this->option('output');

        $this->info("Pulling secrets from KeyEnv ({$environment})...");

        try {
            $client = KeyEnv::create($token);
            $content = $client->generateEnvFile($projectId, $environment);

            file_put_contents(base_path($output), $content);

            $this->info("Secrets written to {$output}");
            return 0;
        } catch (\Exception $e) {
            $this->error("Failed: {$e->getMessage()}");
            return 1;
        }
    }
}

keyenv:env Command

// app/Console/Commands/KeyEnvEnv.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use KeyEnv\KeyEnv;

class KeyEnvEnv extends Command
{
    protected $signature = 'keyenv:env
        {--env= : KeyEnv environment to list}';

    protected $description = 'List available KeyEnv environments';

    public function handle(): int
    {
        $token = config('keyenv.token');
        $projectId = config('keyenv.project_id');

        if (!$token || !$projectId) {
            $this->error('KEYENV_TOKEN and KEYENV_PROJECT must be set');
            return 1;
        }

        try {
            $client = KeyEnv::create($token);
            $environments = $client->listEnvironments($projectId);

            $this->table(
                ['Name', 'Inherits From'],
                collect($environments)->map(fn($env) => [
                    $env->name,
                    $env->inheritsFrom ?? '-',
                ])
            );

            return 0;
        } catch (\Exception $e) {
            $this->error("Failed: {$e->getMessage()}");
            return 1;
        }
    }
}

Register commands in app/Console/Kernel.php (Laravel 10) or they auto-register in Laravel 11:

protected $commands = [
    Commands\KeyEnvPull::class,
    Commands\KeyEnvEnv::class,
];

Complete Example

Here's a complete setup for a production Laravel application:

Service Provider

// app/Providers/KeyEnvServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use KeyEnv\KeyEnv;

class KeyEnvServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__.'/../../config/keyenv.php', 'keyenv');

        $this->app->singleton(KeyEnv::class, function () {
            $token = config('keyenv.token');
            if (!$token) {
                return null;
            }
            return KeyEnv::create($token, config('keyenv.timeout', 30));
        });

        $this->loadSecrets();
    }

    protected function loadSecrets(): void
    {
        if (!config('keyenv.enabled')) {
            return;
        }

        $token = config('keyenv.token');
        $projectId = config('keyenv.project_id');

        if (!$token || !$projectId) {
            return;
        }

        try {
            $client = KeyEnv::create($token, config('keyenv.timeout', 30));
            $environment = $this->resolveEnvironment();
            $client->loadEnv($projectId, $environment);
        } catch (\Exception $e) {
            // Log but don't crash - allows fallback to .env
            logger()->error('KeyEnv: Failed to load secrets', [
                'error' => $e->getMessage(),
            ]);
        }
    }

    protected function resolveEnvironment(): string
    {
        // Explicit override
        if ($env = env('KEYENV_ENV')) {
            return $env;
        }

        // Map Laravel environment
        $laravelEnv = app()->environment();
        return config("keyenv.environments.{$laravelEnv}", $laravelEnv);
    }

    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../../config/keyenv.php' => config_path('keyenv.php'),
            ], 'keyenv-config');
        }
    }
}

Database Configuration

// config/database.php
return [
    'default' => env('DB_CONNECTION', 'pgsql'),

    'connections' => [
        'pgsql' => [
            'driver' => 'pgsql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '5432'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8',
            'prefix' => '',
            'prefix_indexes' => true,
            'search_path' => 'public',
            'sslmode' => 'prefer',
        ],

        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
        ],
    ],

    'redis' => [
        'client' => env('REDIS_CLIENT', 'phpredis'),
        'default' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_DB', '0'),
        ],
        'cache' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_CACHE_DB', '1'),
        ],
    ],
];

Services Configuration

// config/services.php
return [
    'mailgun' => [
        'domain' => env('MAILGUN_DOMAIN'),
        'secret' => env('MAILGUN_SECRET'),
        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    ],

    'postmark' => [
        'token' => env('POSTMARK_TOKEN'),
    ],

    'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],

    'stripe' => [
        'key' => env('STRIPE_KEY'),
        'secret' => env('STRIPE_SECRET'),
        'webhook' => [
            'secret' => env('STRIPE_WEBHOOK_SECRET'),
            'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
        ],
    ],

    'pusher' => [
        'app_id' => env('PUSHER_APP_ID'),
        'app_key' => env('PUSHER_APP_KEY'),
        'app_secret' => env('PUSHER_APP_SECRET'),
        'app_cluster' => env('PUSHER_APP_CLUSTER'),
    ],
];

Setting Up Your Deployment

Required Environment Variables

Set these in your deployment platform (not in KeyEnv):

VariableDescription
KEYENV_TOKENYour KeyEnv service token
KEYENV_PROJECTYour KeyEnv project ID
APP_ENVLaravel environment (production, staging, local)
APP_KEYLaravel encryption key (optional if in KeyEnv)

Secrets to Store in KeyEnv

Store these secrets in KeyEnv for each environment:

SecretDescriptionExample
APP_KEYLaravel encryption keybase64:...
DB_HOSTDatabase hostdb.example.com
DB_PASSWORDDatabase passwordsecure-password
REDIS_PASSWORDRedis passwordredis-password
MAIL_PASSWORDSMTP passwordsmtp-password
STRIPE_SECRETStripe secret keysk_live_...
STRIPE_WEBHOOK_SECRETStripe webhook secretwhsec_...
AWS_SECRET_ACCESS_KEYAWS secret key...
PUSHER_APP_SECRETPusher secret...

Generate Laravel App Key

php artisan key:generate --show
# Copy the output to KeyEnv as APP_KEY

Local Development

For local development, you have several options:

Option 1: Use KeyEnv in Development

Set KEYENV_TOKEN and KEYENV_PROJECT in your .env file:

# .env
KEYENV_TOKEN=env_dev_...
KEYENV_PROJECT=your-project-id
KEYENV_ENABLED=true

Option 2: Pull to .env

Use the CLI or Artisan command to generate a local .env:

# Using CLI
keyenv pull --env development --output .env

# Using Artisan
php artisan keyenv:pull --env=development

Option 3: Disable KeyEnv Locally

Use a local .env file without KeyEnv:

# .env
KEYENV_ENABLED=false
DB_HOST=127.0.0.1
DB_PASSWORD=secret
# ... other local secrets

Testing

For tests, disable KeyEnv by setting the environment variable:

// phpunit.xml
<env name="KEYENV_ENABLED" value="false"/>
<env name="APP_ENV" value="testing"/>

Or in your test setup:

// tests/TestCase.php
protected function setUp(): void
{
    putenv('KEYENV_ENABLED=false');
    parent::setUp();
}

Troubleshooting

"KEYENV_TOKEN not set"

Ensure KEYENV_TOKEN is set in your deployment platform's environment variables (Forge, Vapor, Render, etc.).

"Project not found"

Verify KEYENV_PROJECT matches your project ID in the KeyEnv dashboard.

"Environment not found"

Check that the environment name exists in your KeyEnv project. Laravel's local environment maps to KeyEnv's development by default.

Config caching issues

When using php artisan config:cache, ensure KeyEnv secrets are loaded before caching:

# In deployment script
php artisan config:clear
# Secrets will be loaded fresh on next request
php artisan config:cache

Secrets not loading

  1. Verify KeyEnvServiceProvider is registered and runs early
  2. Check that loadEnv() runs before Laravel reads config
  3. Verify the service token has read access to the environment
  4. Check for typos in secret key names

Performance

loadEnv() makes a single API call at application boot. Secrets are cached in $_ENV for the lifetime of the request. There's no additional overhead after initialization.

For optimal performance with Laravel Octane or similar long-running processes, secrets remain cached in memory.

On this page