spatie/symfony-ignition-bundle
Spatie Symfony Ignition Bundle replaces Symfony’s default exception pages with Ignition’s beautiful, customizable error pages, including dark mode. Install via Composer as a dev dependency and enable the bundle in config/bundles.php.
Ignition swaps Symfony's dev exception page for the same renderer Laravel uses: stack frames with code excerpts, request/route context, and (optionally) AI-generated fix suggestions. It is a dev-only tool — install it and forget it exists in production.
composer require --dev spatie/symfony-ignition-bundle
Register it for dev only, in config/bundles.php:
return [
// ...
Spatie\SymfonyIgnitionBundle\IgnitionBundle::class => ['dev' => true],
];
Since 1.0.0 the Symfony Flex recipe is approved, so a fresh composer require into a Flex app typically writes both the bundle entry and config/packages/ignition.yaml for you — verify before hand-editing.
Where to look first: bin/console debug:config ignition is the authoritative list of keys this bundle actually exposes. Treat the README snippet as an example, not the full surface.
Baseline config:
# config/packages/ignition.yaml
when@dev:
ignition:
application_path: '%kernel.project_dir%'
dark_mode: false
should_display_exception: '%kernel.debug%'
force_html_response: false
First use case: throw something and hit the route.
#[Route('/orders/{id}', name: 'order_show')]
public function show(int $id, OrderRepository $orders): Response
{
$order = $orders->find($id) ?? throw new \RuntimeException("No order {$id}");
// ...
}
You get the Ignition page instead of Symfony's. If you don't, the exception is almost certainly being thrown before the kernel.exception event fires (see Gotchas) or the bundle isn't enabled for the current env.
Requirements worth checking up front: PHP 8.1+ (8.0 dropped in 1.1.0), Symfony 6.1+/7.x, and 1.3.1 or later if you're on Symfony 8.
Scope configuration to dev, always. The when@dev wrapper plus ['dev' => true] in bundles.php is belt-and-braces; keep both. If you also want it in test, add 'test' => true deliberately — not by default, because HTML error bodies can break JSON assertions in functional tests.
Make the request format work for you. Since 1.3.0, non-HTML requests pass through to Symfony's normal handling. That's the right default for a JSON API. When you're debugging a fetch/Axios call from a SPA and want the full Ignition page in the browser tab, flip it temporarily:
when@dev:
ignition:
force_html_response: true
Keep it false in your committed config and toggle it locally, otherwise you'll get HTML injected into API responses your frontend tries to JSON.parse, and you'll chase a phantom "unexpected token <" bug.
Point application_path at the root your code actually lives at. In Docker, a monorepo, or a DDEV setup where %kernel.project_dir% differs from the path baked into stack traces, source excerpts and frame collapsing get it wrong. Setting it explicitly to the mount point fixes both.
AI solutions, safely. openai_key enables the "solve with AI" affordance:
when@dev:
ignition:
openai_key: '%env(OPENAI_API_KEY)%'
Keep the key in .env.local (gitignored) or a developer's shell environment, never in .env or the committed YAML. Note the privacy implication: the stack trace and surrounding source lines leave your machine. On client work or anything under NDA, leave the key unset — everything else in Ignition works without it.
Use it as your primary "why did this break?" loop. The practical workflow that replaces tail -f var/log/dev.log:
dump().dump()/dd (or bin/console + profiler) for state that isn't in the exception context.Pair it with the profiler rather than instead of it. Ignition renders the exception response; the Symfony profiler still records the request. For an exception you keep hitting, open the profiler's Exception panel for the full trace and the Logs panel for deprecations that Ignition won't surface.
Custom error pages stay testable in test/prod. Because the bundle is dev-only, your templates/bundles/TwigBundle/Exception/error404.html.twig and custom framework.error_controller are exercised only outside dev. Run your error-page tests with APP_ENV=test and APP_DEBUG=0; if you enable Ignition in test, those tests will assert against Ignition's HTML and pass for the wrong reason.
bin/console failures still render Symfony's console error output. If you're debugging a failing command, Ignition will not help; use -vvv and the log.kernel.exception listener with a higher priority than Ignition's won't reach the renderer. Symptom: "Ignition worked all morning, now I get the plain Symfony page." Run bin/console cache:clear and check var/log/dev.log — the broken thing is usually a service definition or an env var, not Ignition.should_display_exception: '%kernel.debug%' is load-bearing. With APP_DEBUG=0 you'll get the generic page even in dev. If a colleague's .env.local sets APP_DEBUG=0, Ignition silently looks broken.spatie/ignition's own README — Flare reporting keys, custom solution providers, housekeeping — are not all plumbed through ignition.yaml. Confirm against debug:config ignition before assuming a key is supported, and reach for the underlying Ignition configuration (or a small compiler pass / listener) if you need more.Response; any listener you wrote that also builds a response for HTML requests in dev will compete on priority. Verify custom error handling in test, and don't judge it by what dev shows you.composer install --no-dev in the build image, bundle registered for dev only, and no openai_key in any committed file. Because the dependency is require-dev, a --no-dev install makes it structurally impossible to leak the error page — that's the real guarantee.application_path doesn't resolve inside the container, frames render without excerpts. Not fatal, but confusing.bin/console cache:clear after editing ignition.yaml; the bundle's config is compiled into the container like any other.require-dev, and it's a low-risk convenience rather than infrastructure.kernel.exception event. Custom behavior — extra context, your own solution providers, tab additions — belongs in a service tagged for Ignition's run-time config or a small listener registered after it, not in forking the bundle.How can I help you explore Laravel packages today?