Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ignition Laravel Package

spatie/ignition

Spatie Ignition is a beautiful, customizable error page for PHP apps, with a dark mode and simple registration. Laravel, Symfony, and Drupal integrations are available.

View on GitHub
Deep Wiki
Context7

Getting Started

For a Laravel developer, the important thing to internalize up front is that you almost never install spatie/ignition directly — Laravel ships spatie/laravel-ignition (which wraps this core package), and that's what renders the error page you already see locally. So "getting started" here is less about composer require and more about learning the extension points this package exposes so you can bend the error page to your app's needs.

The first place to look is the exception page itself: throw something in a local route and note the two things you can customize — the "Solutions" panel and the Flare reporting pipeline.

Minimal mental model:

// What laravel-ignition effectively does under the hood:
\Spatie\Ignition\Ignition::make()
    ->applicationPath(base_path())      // trims base path from stack traces
    ->setTheme('dark')                  // optional
    ->shouldDisplayException(app()->isLocal())
    ->register();

Your first practical use case: teach the error page to solve your own domain exceptions. Any exception in your app can implement ProvidesSolution so that instead of a raw stack trace, you get a title, description, and documentation link right in the browser:

use Spatie\Ignition\Contracts\ProvidesSolution;
use Spatie\Ignition\Contracts\Solution;

class TenantNotProvisionedException extends Exception implements ProvidesSolution
{
    public function getSolution(): Solution
    {
        return new TenantNotProvisionedSolution($this->tenantId);
    }
}

That's the highest-ROI first step — it turns recurring "what does this even mean?" exceptions into self-documenting ones.

Implementation Patterns

1. Attach solutions directly to exceptions (the simple path)

Use ProvidesSolution when the fix is intrinsic to the exception. Good candidates: misconfiguration, missing env vars, invalid credentials, deprecated API usage. Return a Solution with title, description, and getDocumentationLinks() pointing at your internal runbook.

class MissingWebhookSecretException extends Exception implements ProvidesSolution
{
    public function getSolution(): Solution
    {
        return new class implements Solution {
            public function getSolutionTitle(): string { return 'Webhook secret not configured'; }
            public function getSolutionDescription(): string {
                return 'Set WEBHOOK_SECRET in .env and run `php artisan config:clear`.';
            }
            public function getDocumentationLinks(): array {
                return ['Webhook setup' => 'https://wiki.internal/webhooks'];
            }
        };
    }
}

2. Use solution providers to solve foreign exceptions

When you can't modify the exception class (vendor code, framework exceptions, third-party SDKs), implement HasSolutionsForThrowable. This is where you get leverage: a provider can pattern-match on message/class and return contextual guidance.

class StripeSolutionProvider implements HasSolutionsForThrowable
{
    public function canSolve(Throwable $t): bool
    {
        return $t instanceof \Stripe\Exception\ApiErrorException
            && str_contains($t->getMessage(), 'No such customer');
    }

    public function getSolutions(Throwable $t): array
    {
        return [new StripeCustomerMissingSolution()];
    }
}

Register it in the Laravel service provider bootstrap (in laravel-ignition you register providers via the package's config, or in bare Ignition via addSolutionProviders([...])). This is the pattern to standardize across a team: one provider per external system that frequently bites you (Stripe, S3, Redis, queue workers).

3. AI solutions — optional and gated

OpenAiSolutionProvider is genuinely useful for novel errors, but it means sending exception context off-box. Treat it as a local-only convenience:

$ai = new OpenAiSolutionProvider(config('services.openai.key'));
$ai->applicationType('Laravel ' . app()->version());
$ai->useCache($psr16Cache, 3600);   // avoid repeat billing for the same error

Key workflow tips:

  • Always enable caching (useCache) in local dev — you'll hit the same error dozens of times while debugging.
  • Pass applicationType() — it measurably improves suggestion quality.
  • Do not register it in staging/production. Wire it conditionally to app()->isLocal().

4. Flare integration in production (the actual production story)

The error page is local; the production counterpart is Flare reporting. The pattern to follow:

->runningInProductionEnvironment(app()->isProduction())
->sendToFlare(config('services.flare.key'))
->configureFlare(function (Flare $flare) {
    $flare->context('Tenant', tenant()?->id);
    $flare->group('Deploy', ['commit' => config('app.commit'), 'env' => app()->environment()]);
    $flare->censorRequestBodyFields(['password', 'token', 'card_number']);
    $flare->anonymizeIp();
})
  • Use context() for single key-value pairs that you want surfaced prominently (tenant, user, request id).
  • Use group() for a bundle of related diagnostics (deploy metadata, feature flags).
  • Censoring is not optional. Any app with a login form should call censorRequestBodyFields(['password']). Add your API tokens too.
  • anonymizeIp() if you operate under GDPR or similar.

5. Flare middleware for cross-cutting request data

When you want the same enrichment on every report (trace id, tenant, feature flags), register a FlareMiddleware rather than duplicating calls to context():

class RequestContextMiddleware implements FlareMiddleware
{
    public function handle(Report $report, Closure $next)
    {
        $report->context('request_id', request()->header('X-Request-Id'));
        return $next($report);
    }
}

This is the cleanest place to hook in correlation IDs so debugging in Flare ties back to your logs.

6. Gate rendering with shouldDisplayException

Never leak Ignition in production. In Laravel this is configured via config/ignition.php ('editor', plus the framework's APP_DEBUG), but if you're using core Ignition in a non-Laravel PHP app or a bespoke bootstrap, call:

->shouldDisplayException($env === 'local' || $env === 'development')

Belt-and-braces: also never let a ProvidesSolution accidentally embed credentials in its description — it renders to the browser.

Gotchas and Tips

1. spatie/ignition ≠ spatie/laravel-ignition. In a Laravel app, adding spatie/ignition directly will not integrate — you need the Laravel wrapper. If you're on an older Laravel version and error pages look broken, mismatch between laravel-ignition and core ignition is the usual culprit. Check composer why spatie/ignition.

2. applicationPath() must be your real base path. Pass the actual app root (in Laravel: base_path()). Passing a parent directory trims too much and stack traces lose their file context; passing a subdirectory leaves ugly absolute paths.

3. Solution providers run on every exception. canSolve() is called on each throwable for each registered provider — keep it cheap. Avoid DB queries or HTTP calls inside canSolve; guard with class checks first. Do the expensive lookup inside getSolutions().

4. AI provider can be slow and can fail open. If OpenAI times out, the whole error page can stall before rendering. Wrap it: register it last, use caching, and be comfortable with the page falling back if it errors. Also — the docs are explicit that solutions may be wrong for your context; treat them as hints, not gospel.

5. Censorship is field-name based and shallow. censorRequestBodyFields(['password']) matches top-level request fields. Nested payloads (user.password) or values inside JSON bodies may slip through. If you have deeply nested sensitive data, add a middleware that strips it before sending.

6. runningInProductionEnvironment vs shouldDisplayException do different things. The former controls both rendering and Flare sending; the latter controls only rendering. Don't reach for shouldDisplayException(true) as "show the page in prod" — that's a security incident waiting to happen.

7. Dark mode is a string, not a boolean. setTheme('dark') — pass 'light' to revert. Typos silently fall back to default; no exception thrown.

8. Extension point hierarchy — pick the right hook:

  • Exception is yours → implement ProvidesSolution.
  • Exception is someone else's → HasSolutionsForThrowable provider.
  • Need data on every Flare report → FlareMiddleware.
  • Need data on specific reports → configureFlare(fn ($flare) => $flare->context(...)).

9. Debugging "why isn't my solution showing?" Solutions only render if the page itself renders — check APP_DEBUG is on and that the exception is actually unhandled. canSolve returning false due to a type hint mismatch (e.g., expecting Exception but getting an Error) is a common silent failure: use Throwable in signatures.

10. getDocumentationLinks() is your leverage point. Link every solution to an internal runbook or wiki page. This is what turns Ignition from "pretty error page" into "developer onboarding tool" — the next person to hit the error gets the fix without asking Slack.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table