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

Laravel Slower Laravel Package

halilcosdu/laravel-slower

Laravel Slower captures your slowest database queries and uses AI (OpenAI, Claude, Gemini, or custom models) to suggest indexes and rewrites, with a built-in dashboard at /slower and no frontend setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install in three commands, then look at the dashboard before you write any config.

composer require halilcosdu/laravel-slower
php artisan vendor:publish --tag="slower-migrations"
php artisan migrate

At this point capture is already live — a DB::listen hook is timing every query on every connection and writing anything above the threshold into slow_logs. The package is intentionally zero-config to start, so resist the urge to tune before you have data.

Where to look first, in this order:

  1. /slower (local environment only by default). This is your primary interface: overview stats, an Events list, and a Grouped list. If you see rows here after a few minutes of clicking around your app, capture is working.
  2. config/slower.php — publish it now with php artisan vendor:publish --tag="slower-config". The single most consequential value is threshold (milliseconds). If your dashboard is empty, that's the first number to lower; if it's flooded, that's the first number to raise. Also check dashboard.path, dashboard.per_page, dashboard.analyze_pending_limit, and the ai_payload block while you're in there.
  3. The Grouped view — not the Events view. The Events view is a firehose; the Grouped view is the one that tells you what to fix, because it collapses 10,000 executions of the same statement into one row with an occurrence counter, avg/max duration and last-seen timestamp.
  4. A query detail page — this is where the package earns its keep. You get the formatted SQL, the parameterized statement, bindings, and crucially the origin: route name, URI pattern, Controller@action, or job/command class, plus the first file:line of your code in the stack.
  5. config/prism.php — only when you're ready for AI. Credentials live in Prism's config, not Slower's; you select a provider with SLOWER_AI_SERVICE (openai, anthropic, gemini, or a custom driver) and put the key where Prism expects it.

First use case — the 20-minute win. Open Grouped, sort by occurrence count descending, take the top entry, click into it, and read the origin. Nine times out of ten on a Laravel app the answer is a missing eager load or an index on a foreign key, and you'll know which controller to open before you ever touch the AI button. Fix it, wait for new traffic, and confirm the group's last-seen time stops advancing. That loop — group → drill → fix → verify — is the whole product; the AI analysis is an accelerant on step three, not the point of the tool.

Implementation Patterns

Split behaviour by environment, not by feature. The same package should be loud in dev and quiet in prod, and the env vars let you do that without code changes:

# .env.local — see everything, no AI cost
SLOWER_ANALYZE_QUEUE=
SLOWER_SAMPLE_RATE=1.0

# .env.production — representative sample, analysis off the request path
SLOWER_SAMPLE_RATE=0.1
SLOWER_MAX_PER_EXECUTION=50
SLOWER_ANALYZE_QUEUE=default

SLOWER_SAMPLE_RATE is the knob that makes this safe to leave on under real traffic — 0.1 captures a representative tenth of threshold-crossing queries. Accept immediately that counts become approximate; the grouped view still ranks offenders correctly, which is all you need. SLOWER_MAX_PER_EXECUTION caps a single runaway request or job that would otherwise dump hundreds of rows into the table.

Schedule the three commands rather than clicking buttons.

// routes/console.php or a scheduler service provider
Schedule::command('slower:analyze')->hourly()->withoutOverlapping();
Schedule::command('slower:clean', ['--days' => 30])->daily();

slower:analyze handles bulk analysis of unanalyzed captures — this is how you get AI recommendations across your whole backlog instead of clicking row by row. slower:clean enforces retention, which is not optional given what's in that table. slower:fingerprint is a one-shot migration command, not a scheduled one.

Alert on first sight, not on every capture. The two events have different jobs:

use HalilCosdu\Slower\Events\SlowQueryFirstSeen;
use HalilCosdu\Slower\Events\SlowQueryCaptured;

// New query shape crossed the threshold — the actionable signal.
Event::listen(SlowQueryFirstSeen::class, function ($event) {
    Notification::route('slack', config('services.slack.webhook'))
        ->notify(new NewSlowQueryShape($event->log ?? $event));
});

// High volume, use sparingly — this fires on every capture above threshold.
Event::listen(SlowQueryCaptured::class, fn ($event) => Metrics::increment('slower.captured'));

Because fingerprints are stable per query shape, SlowQueryFirstSeen gives you exactly one Slack message per distinct problem — including one when a new regression appears after a deploy — instead of a message per execution. That's the pattern that survives contact with a real on-call rotation. Check the event class signatures before wiring listeners; the payload shapes are what you'd expect from the detail page.

Turn on queued analysis before turning on AI in production. An LLM round-trip is seconds long. With SLOWER_ANALYZE_QUEUE unset, analysis is synchronous and needs no worker — fine locally, wrong on a request path. Set it to any queue name and both the scheduled command and the dashboard's Analyze buttons dispatch unique background jobs, so the same query isn't analyzed twice concurrently and a 30-second provider call never blocks a user. In production, the dashboard Analyze button is the only synchronous-feeling path left, and it's rate-limited and capped by dashboard.analyze_pending_limit (default 10) — clicking it on a 500-row backlog analyzes 10 and stops.

Treat the AI payload default as a feature, and opt in deliberately. By default only the parameterized SQL, schema/index definitions, origin context and a read-only EXPLAIN plan leave your app — no binding values, no literals, and never a captured user id even when SLOWER_CAPTURE_USER_ID=true. If your provider genuinely needs real values, opt in and install a redactor in the same commit:

// config/slower.php
'ai_payload' => [
    'send_raw_sql'  => env('SLOWER_AI_SEND_RAW_SQL', false),
    'send_bindings' => env('SLOWER_AI_SEND_BINDINGS', false),
    'redactor'      => App\Support\SlowerRedactor::class,
],

Implement HalilCosdu\Slower\Contracts\PayloadRedactor and note that it covers every outbound path including the EXPLAIN output, which can echo literal values on some drivers. If you can't justify a redactor, set SLOWER_AI_RECOMMENDATION_USE_EXPLAIN=false instead — you lose some plan detail, you keep literals out.

Keep the gate as tight as the SQL. The dashboard ships with the same protection model as Telescope and Horizon: open in local, closed everywhere else until you define a gate.

// AppServiceProvider::boot()
Gate::define('viewSlower', fn ($user = null) => $user?->isAdmin() ?? false);

Combine that with dashboard.middleware (keep HalilCosdu\Slower\Http\Middleware\Authorize in the array — it's what invokes the gate), or move the whole thing off the public surface with SLOWER_DASHBOARD_DOMAIN and SLOWER_DASHBOARD_PATH.

Use it alongside Telescope, not instead of it. Telescope answers "what did this request do"; Slower answers "which query shape is hurting us and where does it come from." When a Telescope request shows 400 queries, jump to Slower's Grouped view to find out whether that's one bad pattern repeated or 400 distinct statements — the fingerprinting is the part Telescope doesn't give you.

Gotchas and Tips

The publish tag names are not consistent. Migrations are slower-migrations, config is slower-config, but views are laravel-slower-views. Typing slower-views silently publishes nothing and you'll spend ten minutes wondering why your restyle had no effect. php artisan vendor:publish with no tag lists everything available — use it when a tag doesn't take.

An empty dashboard usually means the threshold, not a bug. Defaults are tuned to be quiet. Lower threshold in config/slower.php and re-check before you start debugging the listener.

SLOWER_DASHBOARD_ENABLED=false removes the routes entirely — it is not a soft hide. That's the right call once you've moved on from a slow-query investigation, because the routes, the gate, and the rendered SQL all disappear together.

The table is a PII liability by design. slow_logs holds raw SQL and bindings, which means emails, tokens and ids. Set a retention policy with slower:clean --days=N on day one, not day ninety. Note the footgun in the cleanup UI: 0 days means wipe everything, not keep everything.

Upgrading across versions requires php artisan slower:fingerprint. The fingerprint algorithm is versioned, so old rows carry stale fingerprints and won't group with new captures — you'll see duplicate groups until you run it. It's chunked and idempotent, safe to interrupt and re-run, but it's a manual step nobody remembers.

Groups are scoped per connection. If you run multiple connections (read replicas, a separate reporting DB), the same statement on two connections will appear as two groups. That's usually what you want; just don't misread it as a duplicate-row bug.

EXPLAIN is best-effort context, not a guarantee. It's read-only and safe to send, but on some drivers it echoes literal values — which is precisely why the redactor covers it. In a regulated environment, disable it rather than trusting the driver.

A misconfigured redactor throws; it does not degrade silently. A class that doesn't implement the contract fails loudly on the first AI call. That's the correct behaviour, but it means a typo'd namespace surfaces as a failed analysis job rather than a warning — check your failed-jobs table if AI recommendations stop appearing after you add a redactor.

The circuit breaker hides outages. If writing a capture itself fails (full disk, dropped table), Slower backs off for 60 seconds rather than adding a failed INSERT to every slow query. Failures are report()ed, so they land in your exception handler — but if you notice a suspiciously flat line in your captured-per-hour metric, look for those reports before you conclude traffic dropped.

The self-capture guard means you can't audit Slower with Slower. Queries touching its own table are never captured — necessary, since otherwise the logger feeds itself, but it does mean the dashboard's own queries are invisible to it. Use Telescope or DB::listen directly if you need to profile the dashboard.

Sampling makes the numbers honest-but-approximate. With SLOWER_SAMPLE_RATE < 1.0, occurrence counts are estimates. Don't chart them as absolute request volumes or use them to justify capacity; use them to rank.

Extension points worth knowing about, in order of likely usefulness: the PayloadRedactor contract (privacy), a custom AI driver (one method — the escape hatch for a self-hosted model), the two events (alerting), published views (branding), and dashboard.middleware (auth scheme). The README also has a Programmatic usage section worth reading before you reach into the slow_logs model directly — read it first rather than guessing at class names, since the query/fingerprint helpers are the part most likely to change between minor versions.

Version floors are real. PHP 8.3+ and Laravel 11–13. On a Laravel 10 app this simply won't install, and there is no older branch to fall back to.

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.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin