The Dev Log › Tips & Cheat Codes
Debugging Laravel Like a Pro: Telescope, Pail, dd() and Friends
By Jezer Niel Blanca, Full Stack Developer ·
·
6 min read
The debugging method and tools I reach for in Laravel, from dd() and ddRawSql() to contextual logs, Pail, Telescope, strict mode and Xdebug.
Every developer debugs. The difference between a frustrating afternoon and a ten-minute fix is usually not intelligence, it's tooling and method. Laravel ships with an excellent set of debugging tools, and the ecosystem adds even more. In this post I'll walk through the tools I actually use, from the humble dd() to Telescope and Pail, and the order I reach for them in when something breaks.
Start With a Method, Not a Tool
Before touching any tool, I run through the same short checklist. It saves more time than any package:
- Reproduce it. If you can't make the bug happen on purpose, you can't prove you fixed it.
- Read the whole error. The exception message, the file, the line, and the first few frames of the stack trace that point to your own code, not the vendor folder.
- Narrow the scope. Is it the route, the controller, the query, the view, or the front end? Cut the problem in half until it's small.
- Change one thing at a time. Multiple simultaneous changes make it impossible to know what actually fixed it.
- Write a test that fails. Once you understand the bug, capture it in a test so it can never quietly return.
Debugging is a search problem. Every step should cut the space where the bug could be hiding.
dd(), dump() and Their Smarter Cousins
The classics are still great. dump() prints a variable and keeps running, while dd() dumps and stops execution.
dump($request->all());
dd($order, $order->items);
A few variations I use constantly:
- Collections have them built in. Chain
->dump() in the middle of a pipeline to see the data at that step without breaking the chain.
$totals = $orders
->filter(fn (Order $order) => $order->isPaid())
->dump()
->groupBy('customer_id')
->map(fn ($group) => $group->sum('total'));
- Query builders can show their SQL.
toRawSql() returns the query with bindings filled in, and ddRawSql() dumps it and stops. This is the fastest way to answer "why is this query returning nothing?"
Post::query()
->where('is_published', true)
->whereDate('published_at', '<=', now())
->ddRawSql();
- Inertia requests show dumps too. If you call
dd() during an Inertia visit in local development, the output appears in a modal instead of breaking the page silently.
Just remember to remove them. A stray dd() in a pull request is a rite of passage, but it's better caught by a search before committing.
Logs You Can Actually Read: Log Context and Pail
When a bug only happens in certain conditions, logs beat dumps. Laravel's Log facade accepts a context array, which makes entries searchable and far more useful:
Log::info('Checkout started', [
'user_id' => $user->id,
'cart_total' => $cart->total(),
]);
The Context facade takes this further. Anything you add is automatically attached to every log entry for the rest of the request, and even to queued jobs dispatched during it:
use Illuminate\Support\Facades\Context;
Context::add('request_id', (string) Str::uuid());
Then there's Pail, which tails your application logs right in the terminal. It works with any log driver and has filters that make noisy logs manageable:
php artisan pail
php artisan pail --filter="Checkout"
php artisan pail --level=error
php artisan pail --user=1
I keep Pail running in a terminal while I work. When something odd happens in the browser, the answer is often already on screen.
Telescope: A Flight Recorder for Your App
Laravel Telescope records what happens inside your application: requests, exceptions, database queries, queued jobs, mail, notifications, cache operations, scheduled tasks and more. It's the tool I open when I need to see the full story of a request rather than a single variable.
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
Things I use Telescope for most:
- Spotting N+1 queries. Open a request and look at the query list. Twenty nearly identical queries in a row is the classic sign.
- Inspecting jobs. See each job's payload, how many attempts it took, and the exception if it failed.
- Checking outgoing mail. Preview exactly what an email looked like without sending it anywhere.
- Finding slow queries. Telescope flags queries above a configurable threshold.
Because it records so much, Telescope is best kept to local and staging environments. If you install it as a dev dependency, follow the documentation's steps for registering it only in local, and make sure its dashboard is protected wherever it runs.
Catch Problems Before They Become Bugs
Some of the best debugging tools prevent bugs from reaching you at all. In AppServiceProvider, I enable Eloquent's strict checks outside production:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::shouldBeStrict(! $this->app->isProduction());
}
shouldBeStrict() turns on three protections at once: it throws when you lazy load a relationship, when you set an attribute that isn't fillable, and when you access an attribute that wasn't loaded. Each of these is a silent bug in waiting, and now they fail loudly during development instead.
A few Artisan commands are also underrated debugging tools:
php artisan about shows your environment, drivers and cache status at a glance.
php artisan route:list --path=api confirms which routes exist and which middleware they use.
php artisan config:show database shows the resolved config values, which quickly exposes a wrong .env value.
php artisan tinker lets you run code against your app interactively to test assumptions.
Step Debugging With Xdebug
When a bug lives deep inside complex logic, printing values gets tedious. That's when I switch to a real step debugger. Xdebug lets you set breakpoints in your editor, pause execution, inspect every variable, and step through code line by line.
Setup depends on your environment, and tools like Laravel Herd and Sail make it much simpler than it used to be. The payoff is worth it for gnarly bugs: instead of guessing which branch ran, you watch it happen. Conditional breakpoints, which only pause when an expression is true, are especially useful inside loops.
Wrapping up
Good debugging is a method first and a toolbox second. Reproduce the problem, read the whole error, narrow the scope, and then pick the right tool: dd() and ddRawSql() for quick checks, contextual logs and Pail for conditional bugs, Telescope for the full story, strict mode to catch mistakes early, and Xdebug for the really tricky cases. Finish every fix with a test. If you want a team that ships clean, well-tested Laravel code, I'd love to build your next product with you and my team.
Tags: Laravel, Debugging, Telescope, Pail