Type-safe, environment-aware configuration —
for values that are not secrets and not stock Laravel keys.
Much of a mature .env file is configuration that varies by environment — not secrets.
As your application grows, so does your .env. Third-party integrations add values like AI provider names, model selections, payment gateway modes, webhook endpoints, microservice URLs, SMS sender numbers, retry limits, and timeout durations. These are not sensitive tokens or passwords — they are infrastructure decisions that change as you move from local to testing, staging, or production.
Note: Laravel's shipped config/*.php files translate the stock .env keys into config() entries during the LoadConfiguration bootstrap step. Laravel's core managers (DB, cache, queue, mail, log, session, etc.) then read from config() at boot — and two keys, APP_ENV and APP_KEY, are read even earlier, before any service provider runs. Those stock keys should stay in .env. The same goes for every third-party and community package: its own config/*.php reads its keys through env() at that same bootstrap step, so a settings class cannot feed them either — those keys stay in .env too. This package is for the additional configuration that grows as you integrate third-party services, AI providers, payment gateways, internal microservices, and other domain-specific systems.
The litmus test is simple: does knowing this value alone grant access to anything? A database host like prod-db.internal is useless without credentials. An internal microservice URL like https://billing.internal is already protected by network policies, VPNs, and authentication tokens. The address is infrastructure; the credentials are the secret. This package lets you keep each where it belongs — addresses in code, credentials in .env.
You decide what's a secret for your organization. If your team considers a specific URL sensitive, keep it in .env. This package doesn't force any value out — it gives you a typed, reviewable place for the values that don't need to be hidden from your own developers — values that should live in version control so every team member can see the correct setup for each environment, without guessing or asking around.
Laravel's config() is great for values that stay the same across all environments — but it doesn't capture which values should differ per environment or what those differences are. These environment-specific values end up buried in .env files, undocumented and invisible in code review, leaving every new developer who joins the team to guess the correct setup.
The laravel-env-settings package solves this by moving environment-varying configuration into typed, version-controlled PHP classes — making the intended differences between environments explicit, reviewable, and accessible to the whole team.
This package helps you keep .env for secrets and for the stock Laravel keys.
app/Settings/*.php is for the configuration your application adds on top of Laravel — third-party integrations, AI providers, payment gateways, microservice endpoints, and every other non-secret value that varies by environment.
# Stock Laravel keys — stay in .env
# (read by config/*.php → config() → core managers)
APP_NAME=MyApp
APP_ENV=local
APP_KEY=base64:dGVzdGluZ2tleWZvcmxvY2FsZGV2ZWxvcG1lbnQ=
APP_URL=http://myapp.test
DB_CONNECTION=sqlite
CACHE_STORE=array
QUEUE_CONNECTION=sync
MAIL_MAILER=log
# ── Third-party configs piling up ──────────
# AI — using Ollama locally for free
AI_PROVIDER=ollama
AI_TEXT_MODEL=llama3.2
AI_EMBEDDINGS_MODEL=nomic-embed-text
AI_MAX_TOKENS=1024
AI_TEMPERATURE=0.7
OPENAI_API_KEY=sk-proj-FAKE-local-key-xxxxxxxxxx
# ANTHROPIC_API_KEY=??? ← do I need this?
# Payment (sandbox)
PAYMENT_MODE=sandbox
PAYMENT_CURRENCY=USD
PAYMENT_RETRY_ATTEMPTS=1
PAYMENT_WEBHOOK_URL=http://localhost:8000/webhooks/payments
STRIPE_SECRET=sk_test_FAKE51HxYz...
STRIPE_WEBHOOK_SECRET=whsec_test_FAKE...
# SMS / Notifications — just logging locally
SMS_PROVIDER=log
SMS_FROM=+15550000000
NOTIFICATION_CHANNEL=log
NOTIFICATION_RATE_LIMIT=100
NOTIFICATION_SANDBOX=true
# VONAGE_API_KEY=??? ← ask team lead
# VONAGE_API_SECRET=???
# Internal microservices
BILLING_SERVICE_URL=http://localhost:8001
INVENTORY_SERVICE_URL=http://localhost:8002
NOTIFICATION_SERVICE_URL=http://localhost:8003
SERVICE_TIMEOUT=30
SERVICE_RETRY_ATTEMPTS=1
# Are these the right ports? Check with DevOps...
# Stock Laravel keys — stay in .env
# (read by config/*.php → config() → core managers)
APP_NAME=MyApp
APP_ENV=staging
APP_KEY=base64:c3RhZ2luZ2tleWRvbnRjb21taXR0aGlzYW55d2hlcmU=
APP_URL=https://staging.myapp.com
DB_CONNECTION=mysql
DB_HOST=staging-db.internal
DB_PASSWORD=Stg!Pa$$w0rd_2024
CACHE_STORE=redis
QUEUE_CONNECTION=redis
MAIL_MAILER=smtp
# ── Third-party configs keep growing ──────────
# AI — switched to OpenAI for staging
AI_PROVIDER=openai
AI_TEXT_MODEL=gpt-4o-mini
AI_EMBEDDINGS_MODEL=text-embedding-3-small
AI_MAX_TOKENS=2048
AI_TEMPERATURE=0.5
OPENAI_API_KEY=sk-proj-stg-xxxxxxxxxxxxxxxxxxxxxxxx
# Same model as prod? Or cheaper mini?
# Payment (still sandbox on staging)
PAYMENT_MODE=sandbox
PAYMENT_CURRENCY=USD
PAYMENT_RETRY_ATTEMPTS=3
PAYMENT_WEBHOOK_URL=https://staging.myapp.com/webhooks/payments
STRIPE_SECRET=sk_test_STG51AbC...
STRIPE_WEBHOOK_SECRET=whsec_stg_AbC123...
# SMS / Notifications
SMS_PROVIDER=vonage
SMS_FROM=+15551234567
NOTIFICATION_CHANNEL=sms
NOTIFICATION_RATE_LIMIT=50
NOTIFICATION_SANDBOX=true
VONAGE_API_KEY=stg_vonage_key
VONAGE_API_SECRET=stg_vonage_secret
# Internal microservices
BILLING_SERVICE_URL=https://billing-staging.internal
INVENTORY_SERVICE_URL=https://inventory-staging.internal
NOTIFICATION_SERVICE_URL=https://notify-staging.internal
SERVICE_TIMEOUT=15
SERVICE_RETRY_ATTEMPTS=3
# Are staging URLs the same as prod? Who knows...
# Stock Laravel keys — stay in .env
# (read by config/*.php → config() → core managers)
APP_NAME=MyApp
APP_ENV=production
APP_KEY=base64:cHJvZGtleWRvbnRsZWFrdGhpc2V2ZXJvbXk=
APP_URL=https://myapp.com
DB_CONNECTION=mysql
DB_HOST=prod-db.internal
DB_PASSWORD=Pr0d!S3cure#Pa$$w0rd!2024
CACHE_STORE=redis
QUEUE_CONNECTION=redis
MAIL_MAILER=ses
# ── Third-party configs scattered everywhere ──
# AI — copied from staging, is this correct?
AI_PROVIDER=openai
AI_TEXT_MODEL=gpt-4o
AI_EMBEDDINGS_MODEL=text-embedding-3-small
AI_MAX_TOKENS=4096
AI_TEMPERATURE=0.2
OPENAI_API_KEY=sk-proj-prod-LIVE-xxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-prod-xxxxxxxxxxxxxxxx
# What's the right temperature for prod?
# Who changed max_tokens from 2048 to 4096?
# Payment — LIVE MODE
PAYMENT_MODE=live
PAYMENT_CURRENCY=USD
PAYMENT_RETRY_ATTEMPTS=5
PAYMENT_WEBHOOK_URL=https://myapp.com/webhooks/payments
STRIPE_SECRET=sk_live_51ProdRealKey...
STRIPE_WEBHOOK_SECRET=whsec_live_prod_xyz...
# SMS / Notifications — production Vonage
SMS_PROVIDER=vonage
SMS_FROM=+15559876543
NOTIFICATION_CHANNEL=sms
NOTIFICATION_RATE_LIMIT=200
NOTIFICATION_SANDBOX=false
VONAGE_API_KEY=prod_vonage_key
VONAGE_API_SECRET=prod_vonage_secret
# Rate limit was 100, changed to 200 — why?
# Internal microservices
BILLING_SERVICE_URL=https://billing.internal
INVENTORY_SERVICE_URL=https://inventory.internal
NOTIFICATION_SERVICE_URL=https://notify.internal
SERVICE_TIMEOUT=10
SERVICE_RETRY_ATTEMPTS=5
# Someone changed timeout from 15 to 10...
# Was that reviewed? No — .env isn't in Git.
# Stock Laravel keys — stay in .env
# (read by config/*.php → config() → core managers)
APP_NAME=MyApp
APP_ENV=local
APP_KEY=base64:dGVzdGluZ2tleWZvcmxvY2FsZGV2ZWxvcG1lbnQ=
APP_URL=http://myapp.test
DB_CONNECTION=sqlite
CACHE_STORE=array
QUEUE_CONNECTION=sync
MAIL_MAILER=log
# ── Only secrets below ─────────────────────
# AI Provider API Keys
OPENAI_API_KEY=sk-proj-FAKE-local-key-xxxxxxxxxx
# Payment / Stripe Secrets
STRIPE_SECRET=sk_test_FAKE51HxYz...
STRIPE_WEBHOOK_SECRET=whsec_test_FAKE...
# Notification Secrets
# (none needed — using log driver locally)
# ─────────────────────────────────────────
# Everything else — provider choices, model
# names, webhook URLs, microservice endpoints,
# retry counts, timeouts — now lives in typed
# PHP settings classes:
#
# app/Settings/AiSettings.php
# app/Settings/PaymentSettings.php
# app/Settings/NotificationSettings.php
# app/Settings/ExternalApiSettings.php
#
# ✓ Standard Laravel config stays in .env
# ✓ Only real secrets remain here
# ✓ Non-secret config is version-controlled
# ✓ Typed — no more string guessing
# ✓ Per-environment values are explicit
# ✓ Every developer sees the full picture
# Stock Laravel keys — stay in .env
# (read by config/*.php → config() → core managers)
APP_NAME=MyApp
APP_ENV=staging
APP_KEY=base64:c3RhZ2luZ2tleWRvbnRjb21taXR0aGlzYW55d2hlcmU=
APP_URL=https://staging.myapp.com
DB_CONNECTION=mysql
DB_HOST=staging-db.internal
DB_PASSWORD=Stg!Pa$$w0rd_2024
CACHE_STORE=redis
QUEUE_CONNECTION=redis
MAIL_MAILER=smtp
# ── Only secrets below ─────────────────────
# AI Provider API Keys
OPENAI_API_KEY=sk-proj-stg-xxxxxxxxxxxxxxxxxxxxxxxx
# Payment / Stripe Secrets
STRIPE_SECRET=sk_test_STG51AbC...
STRIPE_WEBHOOK_SECRET=whsec_stg_AbC123...
# Notification Secrets
VONAGE_API_KEY=stg_vonage_key
VONAGE_API_SECRET=stg_vonage_secret
# ─────────────────────────────────────────
# Everything else — provider choices, model
# names, webhook URLs, microservice endpoints,
# retry counts, timeouts — now lives in typed
# PHP settings classes:
#
# app/Settings/AiSettings.php
# app/Settings/PaymentSettings.php
# app/Settings/NotificationSettings.php
# app/Settings/ExternalApiSettings.php
#
# ✓ Standard Laravel config stays in .env
# ✓ Only real secrets remain here
# ✓ Non-secret config is version-controlled
# ✓ Typed — no more string guessing
# ✓ Per-environment values are explicit
# ✓ Every developer sees the full picture
# Stock Laravel keys — stay in .env
# (read by config/*.php → config() → core managers)
APP_NAME=MyApp
APP_ENV=production
APP_KEY=base64:cHJvZGtleWRvbnRsZWFrdGhpc2V2ZXJvbXk=
APP_URL=https://myapp.com
DB_CONNECTION=mysql
DB_HOST=prod-db.internal
DB_PASSWORD=Pr0d!S3cure#Pa$$w0rd!2024
CACHE_STORE=redis
QUEUE_CONNECTION=redis
MAIL_MAILER=ses
# ── Only secrets below ─────────────────────
# AI Provider API Keys
OPENAI_API_KEY=sk-proj-prod-LIVE-xxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-prod-xxxxxxxxxxxxxxxx
# Payment / Stripe Secrets
STRIPE_SECRET=sk_live_51ProdRealKey...
STRIPE_WEBHOOK_SECRET=whsec_live_prod_xyz...
# Notification Secrets
VONAGE_API_KEY=prod_vonage_key
VONAGE_API_SECRET=prod_vonage_secret
# ─────────────────────────────────────────
# Everything else — provider choices, model
# names, webhook URLs, microservice endpoints,
# retry counts, timeouts — now lives in typed
# PHP settings classes:
#
# app/Settings/AiSettings.php
# app/Settings/PaymentSettings.php
# app/Settings/NotificationSettings.php
# app/Settings/ExternalApiSettings.php
#
# ✓ Standard Laravel config stays in .env
# ✓ Only real secrets remain here
# ✓ Non-secret config is version-controlled
# ✓ Typed — no more string guessing
# ✓ Per-environment values are explicit
# ✓ Every developer sees the full picture