17 reasons, each a before and an after. Select the problems you have and see how many match.
Settings live in PHP classes, so the tooling is a set of artisan commands rather than a dashboard. Every one of them takes its arguments on the command line — or asks you for them if you leave them off.
| Command | Does | Exits non-zero |
|---|---|---|
| env-settings:make | Scaffolds a settings class and registers it | on failure only |
| env-settings:show | Prints resolved values for the current environment | on failure only |
| env-settings:diff | Compares one class across two environments | on failure only |
| env-settings:check | Reports values left at their placeholder | yes — when incomplete |
Writes the class, its constructor and one factory per environment — then appends it to the
register array so it is never left inert.
Values start as placeholders with a // TODO beside them.
Run
php artisan env-settings:make WebhookSettings \
--properties="endpoint:string,signing_secret:string,retries:int" \
--sensitive="signing_secret"
Settings class created: app/Settings/WebhookSettings.php
→ Registered \App\Settings\WebhookSettings::class in config/env-settings.php
Get
public function __construct(
public string $endpoint,
#[Sensitive] public string $signing_secret,
public int $retries,
) {}
public static function production(): static
{
return new static(
endpoint: '', // TODO: set production value
signing_secret: '', // TODO
retries: 0, // TODO
);
}
--properties
name:type pairs — string, int, float, bool, or an enum class name
--sensitive
names to mark #[Sensitive]; each must appear in --properties
--path
target directory (default app/Settings)
--namespace
explicit namespace, else derived from --path or class_namespace
Resolves every registered class against the current APP_ENV
and prints the values with their types. Pass a class to narrow it to one.
php artisan env-settings:show
php artisan env-settings:show "App\Settings\AiSettings"
[ AiSettings ] — Environment: production
+------------------+--------+------------------------+
| Property | Type | Value |
+------------------+--------+------------------------+
| provider | string | openai |
| text_model | string | gpt-4o |
| embeddings_model | string | text-embedding-3-large |
| max_tokens | int | 8000 |
| temperature | float | 0.2 |
+------------------+--------+------------------------+
--all
Every environment side by side
A settings class states every environment in one file — that is the whole argument for having one. This is the
command that shows it. Without it, “where do these differ?” across four environments took six
diff runs merged in your head.
php artisan env-settings:show --all
php artisan env-settings:show "App\Settings\AiSettings" --all
[ AiSettings ] — development, production, staging, testing
+--------------------+------------------+------------------------+------------------------+------------------+
| Property | development | production | staging | testing |
+--------------------+------------------+------------------------+------------------------+------------------+
| provider * | ollama | openai | openai | ollama |
| text_model * | llama3.2 | gpt-4o | gpt-4o-mini | llama3.2 |
| embeddings_model * | nomic-embed-text | text-embedding-3-large | text-embedding-3-small | nomic-embed-text |
| max_tokens * | 1000 | 8000 | 2000 | 1000 |
| temperature * | 0.9 | 0.2 | 0.5 | 0.9 |
+--------------------+------------------+------------------------+------------------------+------------------+
* = differs somewhere
Real output from this demo. Note testing matching
development exactly — no class here defines
testing(), so it falls through to the fallback.
The Type column is the point: it is the declared PHP type, so an enum-typed property names its enum here. Sensitive values print as ********.
Resolves one class twice — once per environment — and marks every property that differs with a
*. No SSH, no comparing two .env files by eye.
php artisan env-settings:diff "App\Settings\PaymentSettings" staging production
[ PaymentSettings ] — Comparing staging vs production
+------------------+----------+------------+
| Property | staging | production |
+------------------+----------+------------+
| mode * | sandbox | live |
| currency | USD | USD |
| retry_attempts * | 3 | 5 |
| webhook_url * | ******** | ******** |
+------------------+----------+------------+
* = values differ between environments
Masked but still flagged. Both columns read ********,
yet the row keeps its * — comparison happens before masking.
When nothing differs the legend is replaced by No differences found.
Reports properties still sitting at their generated placeholder for a target environment — but only when another environment supplies a real value. The one command here that exits non-zero, so it works in CI as-is.
php artisan env-settings:check --env=production
php artisan env-settings:check
php artisan env-settings:check "App\Settings\AuthSettings"
✗ App\Settings\AuthSettings
domain empty string, but set in development()
timeout 0, but set in development()
[ production ] — 5 settings
classes complete.
Full walkthrough
— what counts as a placeholder, why an all-placeholder class reports as complete, and #[AllowEmpty].
All four share the same input trait, so anything you omit is asked for instead of erroring. Running
env-settings:diff bare walks you through picking a class
and two environments — which is exactly the sequence animated on the
home page.