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
.envfile pattern - Supports Laravel 9.x, 10.x, and 11.x
Installation
composer require keyenv/keyenvQuick 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=productionUsing 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');
" > .envArtisan 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):
| Variable | Description |
|---|---|
KEYENV_TOKEN | Your KeyEnv service token |
KEYENV_PROJECT | Your KeyEnv project ID |
APP_ENV | Laravel environment (production, staging, local) |
APP_KEY | Laravel encryption key (optional if in KeyEnv) |
Secrets to Store in KeyEnv
Store these secrets in KeyEnv for each environment:
| Secret | Description | Example |
|---|---|---|
APP_KEY | Laravel encryption key | base64:... |
DB_HOST | Database host | db.example.com |
DB_PASSWORD | Database password | secure-password |
REDIS_PASSWORD | Redis password | redis-password |
MAIL_PASSWORD | SMTP password | smtp-password |
STRIPE_SECRET | Stripe secret key | sk_live_... |
STRIPE_WEBHOOK_SECRET | Stripe webhook secret | whsec_... |
AWS_SECRET_ACCESS_KEY | AWS secret key | ... |
PUSHER_APP_SECRET | Pusher secret | ... |
Generate Laravel App Key
php artisan key:generate --show
# Copy the output to KeyEnv as APP_KEYLocal 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=trueOption 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=developmentOption 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 secretsTesting
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:cacheSecrets not loading
- Verify
KeyEnvServiceProvideris registered and runs early - Check that
loadEnv()runs before Laravel reads config - Verify the service token has read access to the environment
- 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.