barryvdh/laravel-debugbar
Integrates PHP Debug Bar with Laravel to add a developer toolbar showing queries, routes, views, timings, logs, and memory usage. Easily configurable, with collectors for exceptions, messages, and performance profiling during development.
Install as a dev dependency only — this package should never be deployed to production:
composer require --dev barryvdh/laravel-debugbar
On Laravel 10+ the service provider is auto-discovered via composer.json. Publish the config so you can read it, but treat it as source-of-truth documentation for your installed version:
php artisan vendor:publish --provider="Fruitcake\Debugbar\ServiceProvider" --tag=config
The alias Debugbar and the debugbar() helper are registered for you; if you register the facade manually in config/app.php, guard it with class_exists() so non-dev environments don't blow up.
Where to look first
config/debugbar.php — everything is driven from here. The two switches that matter on day one are enabled (defaults to APP_DEBUG) and the collectors array (queries, views, route, messages, time, memory, exceptions, logs, cache, events, models, mail, gate, auth, session, request, http_client, symfony_request)..env — DEBUGBAR_ENABLED=true|false overrides the config default. Since v3.16 most scalar config values are env()-backed, so you can flip individual collectors per environment without editing the file.First use case: instrument one controller action. Open the page with the bar visible, then add a breadcrumb and a timing measurement to a method you suspect is slow:
use Fruitcake\Debugbar\Facades\Debugbar; // v4; Barryvdh\Debugbar\Facades\Debugbar on v3
public function show(Order $order)
{
Debugbar::info($order->id);
Debugbar::startMeasure('pricing', 'Recalculate pricing');
$total = $this->pricing->recalculate($order);
Debugbar::stopMeasure('pricing');
return view('orders.show', compact('order', 'total'));
}
Refresh, and the value shows under Messages while the duration lands on the Timeline and is broken down on the Request tab. That single loop — instrument, refresh, read the bar — is 90% of daily usage.
The daily triage workflow. For a slow or wrong page, read the tabs in this order:
Use measure() for one-liners. Less noisy than start/stop pairs:
$result = Debugbar::measure('search', fn () => $this->search->run($query), 'Search index');
Guard instrumentation so it costs nothing when disabled. Debugbar's own collectors are cheap; your hand-written loops are not:
if (Debugbar::isEnabled()) {
foreach ($rows as $row) {
Debugbar::addMessage($row->id, 'row'); // label becomes the badge/type
}
}
addMessage($value, $label) is the general-purpose version of info()/warning()/error(). Use Debugbar::addThrowable($e) when you're swallowing an exception and still want to see the trace.
Custom collectors for repeated concerns (tenant resolution, feature flags, an internal API client). Register once in a service provider rather than per-request:
// AppServiceProvider::boot()
if (app()->bound('debugbar') && config('debugbar.enabled')) {
app('debugbar')->addCollector(new TenantCollector());
}
Extend DebugBar\DataCollector\DataCollector and implement getName()/collect(). Registering at runtime is version-stable; some versions also let you map a class in the collectors config array, but confirm against the published file before relying on it.
AJAX / XHR. Set capture_ajax => true, and the bar's open-handler will fetch the stored dataset for the XHR via a X-Debugbar-Id request header. add_ajax_timing => true adds the request duration to the timeline of the parent request. For SPAs hitting JSON endpoints, this is the only way to see a bar — the HTML bar is not injected into JSON responses.
Per-user gating instead of a global switch. On staging, restrict the bar to your team rather than toggling APP_DEBUG:
// AppServiceProvider::boot()
config(['debugbar.enabled' => config('debugbar.enabled') && auth()->user()?->isDeveloper()]);
Pair it with storage.enabled => true (driver file, path under storage/debugbar) so closed-request data survives for the AJAX open handler and for php artisan debugbar:clear.
API and non-HTML work. The bar injects before </body>. For REST, Inertia, or GraphQL, either use the AJAX handler or read the data programmatically:
$data = app('debugbar')->getData(); // array of collector output
Clockwork conversion support (debugbar as a Clockwork data source) is available if your team already uses Clockwork; note that empty tabs are hidden by default since 3.15.0, so an absent tab means "no data", not "broken config".
Editor links. Set 'editor' => 'vscode' (also supports phpstorm, sublime, cursor, windsurf, and more since 3.16.3) so file paths in stack traces and the Views tab are clickable. This is the single highest-value config change for daily use.
APP_DEBUG is true. If the bar doesn't appear, check APP_DEBUG, then DEBUGBAR_ENABLED, then whether php artisan config:cache froze an old value. Cached config ignores later .env edits — php artisan config:clear before assuming a bug.env(). Because config values are env()-backed, changing DEBUGBAR_ENABLED after config:cache has no effect until you re-cache. This bites people in CI and on shared staging.<env name="DEBUGBAR_ENABLED" value="false"/> to phpunit.xml. Left on, the middleware modifies responses and can interfere with content assertions and assertJson/assertSee checks, and adds measurable suite runtime.StreamedResponse, BinaryFileResponse, and responses without a </body> simply get no bar. Likewise a Content-Length header set by a compression middleware can be invalidated — if pages break only with Debugbar on, look at middleware order around your response.dd()/dump() capture changes semantics. With capture_dumps enabled, dumps are collected and dd() may not halt the way you expect. Set capture_dumps => false when you actually need to die-and-dump mid-request.error_handler is on, exceptions are routed through Debugbar. If you see fatal errors rendered differently or missing from storage/logs, turn error_handler => false so Laravel's handler stays authoritative (a fix landed for exactly this in 3.15.3).slow_threshold; unbounded query collection on a report page can exhaust memory or make the bar itself the slowest part of the request. Related: the defer config option defers dataset transport to the open handler, which keeps the initial response small.storage/debugbar is writable by the Octane user, and be aware that per-request collector state must be reset (the package handles this, but your custom collector may not).capture_ajax plus the bar on both parent and component requests; Inertia needs a custom view path configured (support added in 3.16.0). Expect the bar to show the HTML shell rather than the component unless configured.fruitcake/laravel-debugbar, the namespace moved from Barryvdh\Debugbar\* to Fruitcake\Debugbar\*, and the config file was restructured (flat keys plus an options block, collectors as enabled maps). The Debugbar alias and debugbar() helper survive, but hardcoded FQCNs in your own service providers, custom collectors, and class_exists() guards will break silently. Grep for Barryvdh\\Debugbar before bumping, and re-publish the config rather than merging by hand.DataCollector classes, the editor link resolver, the storage driver (file by default), and Debugbar::disable()/enable() for narrow, per-request opt-outs inside code paths where collection is noisy (e.g., a PDF exporter).How can I help you explore Laravel packages today?