spatie/laravel-ignition
Ignition is a beautiful, customizable error page for Laravel apps. It can also share errors publicly on Flare and, with a valid API key, track production errors and notify you when they occur.
It is probably already installed. Every Laravel 11/12/13 skeleton ships spatie/laravel-ignition in require-dev, so the pretty page is what you see on any APP_DEBUG=true exception. Verify with composer show spatie/laravel-ignition, and only reach for composer require spatie/laravel-ignition --dev if it's genuinely missing.
Check the version matrix before debugging "why is the page broken":
| App version | Package version |
|---|---|
| Laravel 11+ / PHP 8.1+ | ^2.10 (2.12 adds Laravel 13) |
| Laravel 10 | pin ^2.9 — 2.10 dropped Laravel 10 |
| Laravel 8/9 | 1.x |
| Laravel ≤7 | facade/ignition (different package) |
Look here first:
config/ignition.php — publish once with php artisan vendor:publish --tag=ignition-config. Until then, read the defaults at vendor/spatie/laravel-ignition/config/ignition.php; don't publish just to see them.vendor/spatie/ignition/src/Solutions/ — this is where the "did you forget to import this class?" / "run php artisan migrate" cards come from, and it tells you what the package already knows about.First use case: set IGNITION_EDITOR=vscode (or phpstorm, sublime, …) in .env, trigger an exception, and click the file link on a stack frame. Then stay on the page: click the Queries tab to see the failing SQL with bindings, and click Run on a solution card to execute the suggested artisan command without leaving the browser. That loop — frame → editor, tab → state, card → fix — is 90% of daily use.
Editor + containerised apps. The single most common local setup task is making "open in editor" work under Sail/Docker, where the container sees /var/www/html but your IDE sees ~/code/app:
// config/ignition.php
'editor' => env('IGNITION_EDITOR', 'phpstorm'),
'remote_sites_path' => env('IGNITION_REMOTE_SITES_PATH', '/var/www/html'),
'local_sites_path' => env('IGNITION_LOCAL_SITES_PATH', '/Users/you/code/app'),
Teach Ignition about your own exceptions. Implement Spatie\Ignition\Contracts\ProvidesSolution on the exception; the page then renders your fix instructions next to the trace:
use Spatie\Ignition\Contracts\ProvidesSolution;
use Spatie\Ignition\Contracts\Solution;
use Spatie\Ignition\Solutions\Solution as BaseSolution;
class TenantNotProvisioned extends Exception implements ProvidesSolution
{
public function getSolution(): Solution
{
return BaseSolution::create()
->setTitle('Tenant database is missing')
->setDescription('Run the provisioning command for this tenant.')
->setSolutionAction(fn () => Artisan::call('tenant:provision', ['id' => $this->tenantId]))
->setActionDescription('Provision tenant');
}
}
Keep getSolution() cheap and side-effect free — it runs on every render of the page. Runnable actions additionally need enable_runnable_solutions => true and belong behind app()->isLocal().
Enrich reports with context. On Laravel 11+, Context::add('tenant_id', $id) is picked up by Ignition (2.5+) and shown in the Context tab and forwarded to Flare. For older versions, add a context() method to your exception.
Flare as the production half. The pretty page only exists in debug; error tracking is the other mode. Set FLARE_KEY per environment and exceptions from prod and from queued jobs (2.3+) get reported, with reports from queues dispatched immediately. Use Flare middleware to scrub anything the default header censoring (2.4.2) misses.
Daily workflows worth adopting:
dump()/->dd() over var_dump in a loop: the Dumps tab collects everything, the response stays clean.'recorders' => [...], or set one to null to switch it off (explicitly supported since 1.6.1) when the Queries tab makes the page crawl on a query-heavy endpoint.'with_stack_frame_arguments' and 'argument_reducers' when you're passing large objects (Eloquent models, uploads) into failing methods.APP_DEBUG=false. Ignition swaps the renderer only in debug mode. If the page is gone, check APP_DEBUG and the cached config (php artisan config:clear) before blaming the package._ignition/* endpoints are real HTTP routes. _ignition/execute-solution executes closures; it must never be reachable in production or on a public staging host. Leave enable_runnable_solutions off outside local, and never combine it with debug on a shared server.ai_solutions + OPEN_AI_KEY sends the exception, trace and context to OpenAI. Turn it off for client work or anything touching PII (IGNITION_AI_SOLUTIONS=false) and say so in the project README..blade.php source with a bounded search radius (widened to ~20 LOC in 1.5.1), so it can point at the wrong line inside @include/@component loops or precompiled views. Cross-check storage/framework/views.ViewException is not your exception class. Ignition wraps view failures, so catch (ViewException $e) in app code or expectException(MyException::class) in tests can surprise you. Tests should use withoutExceptionHandling() rather than asserting on debug HTML.laravel.log, fixed in 1.7.1 and 2.12.0). If your logs are bloated with view payloads, you're on an old pin.RequestTerminated (1.6.2). If you run Octane or queue workers with debug on, watch RSS.PATH (code, phpstorm) — that, not the config key, is usually the problem.hide_solutions may have been flipped by a colleague; an empty solutions area is a config symptom, not a missing feature.FLARE_KEY is a write key. Keep it in .env/secrets, never in a Vite-exposed variable, and rotate if it leaks into a shared error page.composer update — this package rides Laravel's major bumps closely enough that a ^2.0 constraint can silently land you on a version your framework no longer supports.How can I help you explore Laravel packages today?