rawand201/filament-spatie-lighthouse
Seamlessly integrate Spatie Lighthouse with Filament. This plugin adds a native admin panel for exploring and managing Lighthouse GraphQL schemas and queries, helping Laravel developers keep GraphQL workflows visible and accessible inside Filament.
This is a Filament plugin wrapping spatie/lighthouse — you get a page in your panel to run audits, plus commands, scheduling, notifications and an optional REST API. You do not need to know the Spatie package to use it, but you do need a host that can run headless Chrome.
Minimal path:
composer require rawand201/filament-spatie-lighthouse
php artisan vendor:publish --tag="filament-spatie-lighthouse-migrations"
php artisan migrate
Then register the plugin in your panel (app/Providers/Filament/*PanelProvider.php):
->plugin(\Rawand201\FilamentSpatieLighthouse\LighthousePlugin::make())
Publish the config (php artisan vendor:publish --tag="filament-spatie-lighthouse-config" — check the tag, it isn't listed in the notes) and confirm:
APP_URL is reachable from the server itself if you audit your own app.First useful workflow: add your staging URL, run a single synchronous audit on the mobile form factor from the Filament page, and confirm you get all four category scores. Only after that turns green should you wire up scheduling or notifications — you want to isolate "can this host run Lighthouse at all" from "is my config right".
Look first at the config file (form factor, thresholds, result store, scheduling, notifications) and the Audit*Event classes — those are the two surfaces you'll touch most. Ignore the API and raw filesystem storage until you need history at scale.
Result store choice is the first architectural decision. Pick deliberately:
DatabaseResultStore — default for most apps. Gives you history, per-URL trend, bulk export, pruning. Requires the migration and a scheduled model:prune (or the plugin's pruning) to avoid bloat.CacheResultStore — for stateless/read-only deployments or CI-like usage where you don't care about history. Results evaporate on cache:clear and deploys. Don't use it if anyone will ask "what was the score last Tuesday?".ResultStore — implement the interface when you have a non-standard backend (S3, multi-tenant sharding, an existing metrics warehouse). Bind it in a service provider; the UI, commands and API all resolve through the container.Run audits from the UI for exploration, from the queue for everything else. The sync button is great for a one-off sanity check. For anything recurring or user-triggered from a live app, queue the audit — otherwise you'll hit PHP max_execution_time, and in Filament that surfaces as a hung spinner rather than a clean error.
Gate deployments on lighthouse:list in JSON. This is the pattern that pays for the package fastest. In CI, after deploy to staging:
php artisan lighthouse:audit --url=$STAGING_URL --sync --queue=default 2>/dev/null || true
php artisan lighthouse:list --format=json | jq -e 'all(.[]; .performance >= 0.85 and .accessibility >= 0.95)'
Use the config thresholds to define pass/fail in one place rather than hard-coding numbers in CI — keep the config and the CI script aligned, and remember thresholds are per-category and per-metric. If you mix form factors, keep separate threshold sets (a mobile Performance of 0.85 is a different beast from desktop).
Scheduled regression monitoring. Enable scheduling.urls in config and let lighthouse:schedule do the fan-out, or call lighthouse:audit explicitly in your Kernel/routes/console.php. Pair this with notifications:
AuditFailedEvent path is what you want here — a "failed to run" is usually a broken Chrome/network, whereas a low score is a AuditEndedEvent with failing thresholds).Custom listeners for anything beyond built-in notifications. AuditStartingEvent, AuditEndedEvent, AuditFailedEvent are your extension points. Register listeners in EventServiceProvider (or bootstrap/providers.php if you've moved). Keep listeners thin — they run inside the queue worker after the browser has already run, so an accidental HTTP call to a slow webhook blocks the next audit. The natural fan-out is: event → your own job → PagerDuty/Slack/data warehouse. Because events fire for both queued and sync runs, don't assume app()->runningInConsole() is false.
Custom headers for gated environments. This is the killer feature for staging audits behind HTTP basic auth or a WAF. Set an Authorization header or cookie in the audit request config, and audit the protected URL as if from the outside. Combine with a custom user agent so your Cloudflare/WAF rules can allowlist the auditor explicitly rather than fighting bot protection.
API usage (opt-in, token-protected). Enable it only when something external (a dashboard, a deploy pipeline outside Laravel) needs to kick off audits or read results. Protect the token like any other secret, put the routes behind your normal middleware stack (auth, throttle), and prefer tokens scoped to "read results" vs "trigger audit" if the package supports it — if not, at least rotate on a schedule and never expose the routes on the public internet without a rate limit. A Lighthouse audit is an expensive, browser-spawning request; an unthrottled API endpoint is a DoS waiting to happen.
Raw results storage. Set raw_results_driver=filesystem as soon as you have more than a handful of URLs on a schedule. The raw JSON is large, and it's the single biggest source of DB bloat here. Keep the scores and metrics in the DB (they're small and queryable) and the raw payload on disk — this preserves the "history per URL" and export features while keeping the DB lean. Make sure the disk is backed up or expendable, and that the retention policy on disk matches your DB pruning.
Language overrides. For anything user-facing, publish resources/lang/en/lighthouse.php and override rather than wrapping the package in your own translations — this keeps upgrades clean.
--no-sandbox / shared memory flags for containers) and test the container, not your local machine. On managed hosts without root, this may be a blocker — check before promising the feature to anyone.--timeout on the queue worker comfortably above the slowest Lighthouse run (network throttling + mobile emulation can push a page past 60s). A worker with --timeout=60 and an audit that takes 70s will kill the job after Chrome has already started, wasting the run and leaving orphaned processes.CacheResultStore feels convenient in staging, then someone asks for a 30-day trend and you're re-architecting. Choose DatabaseResultStore if "history" is in anyone's mental model of the feature.DatabaseResultStore growth is real and monotonic. Wire model:prune into the scheduler (or a dedicated lighthouse:prune if exposed — check) and verify it actually runs in production, not just locally. Pruning silently not running is the most common "why is this table 4GB" ticket.lighthouse:list --format + your own cursor-based exporter rather than the UI button.thresholds. Conflating the two leads to noisy Slack alerts; route them separately (AuditFailedEvent → on-call/infra, AuditEndedEvent + threshold breach → owning team's channel).ShouldQueue), or at least keep them under a second.PanelProvider does not give you the page in another. Copy-paste errors here produce a silent "the page just isn't there" bug.auth/throttle in your app's real middleware stack — a package route may sit outside your web group.raw_results_driver=filesystem just moves the bloat from MySQL to your storage bucket. Pair it with pruning so the disk and the DB stay in sync — otherwise your per-record HTML report view will 404 on old records even though the score row still exists.v1.0.0 of a young package — pin exact minor versions in composer.json and re-read release notes before upgrading. Anything that touches migrations or the Filament page will likely be a breaking change on the 0.x → 1.x boundary, and there's no long support window to fall back on.How can I help you explore Laravel packages today?