Reader Stacks

How to Print the Raw SQL Query in Laravel Eloquent

toSql(), getBindings(), and DB::listen() — the difference between seeing the query shape and seeing the actual query that ran, bindings included.

How to Print the Raw SQL Query in Laravel Eloquent

There are two different things people mean by "see the SQL" — the query shape with placeholders, and the actual query with real values substituted in — and they need different tools.

Query shape only: toSql()

$query = User::where('active', true)->where('created_at', '>', now()->subDays(7));

echo $query->toSql();
// select * from `users` where `active` = ? and `created_at` > ?

Note the ? placeholders — this shows the query's structure, not the real values bound to it.

Getting the actual bound values

dd($query->getBindings());
// [true, "2026-08-24 10:00:00"]

The shortcut: dd() straight on the query

User::where('active', true)->dd();

Laravel's query builder has built-in dd() and dump() methods that expose the SQL and its bindings in one call — usually the fastest way to inspect a single query while developing.

Capturing every query the app runs

To see every query for a request, not just one you're inspecting — useful for hunting an N+1 problem — register a listener, typically in AppServiceProvider::boot():

use Illuminate\Support\Facades\DB;

DB::listen(function ($query) {
    logger($query->sql, $query->bindings);
    // also available: $query->time (milliseconds)
});

This logs every executed query for as long as the listener is active — genuinely useful for finding N+1 patterns, but noisy enough that it should be gated behind a local-only or debug-only condition, not left running in production.

Reading bindings back into the query manually

If you need one printable string with values substituted in (for pasting into a DB client, not for production logging — values aren't safely escaped for that purpose), Laravel's query log entries include both pieces separately for exactly this reason: combine them yourself rather than trusting a naive string-replace, which can break on values containing ?.

Executed SQL and builder SQL are snapshots at different moments

toSql() asks the builder what it would run at that point. DB::listen() observes queries that actually execute. That difference matters when scopes, eager loading, pagination, relationship access, or a later builder modification produces additional queries you did not see in the initial toSql() output.

Laravel can expose a bound-query representation for debugging

On current query-builder versions, debugging helpers such as dumpRawSql() / ddRawSql() are designed for the common "show me SQL with bindings substituted" workflow. Use them as a development aid, not as a value to execute later:

User::where('email', $email)->ddRawSql();

The database driver still receives SQL and bindings through its normal parameterized execution path. A printable representation is for humans; it should not become a reason to rebuild a parameterized query as raw SQL.

Be careful logging bindings

Bindings can contain passwords, reset tokens, email addresses, payment-related values, or other sensitive data. A query listener that logs every binding may turn the log system into a second copy of data you intentionally protected elsewhere. In production diagnostics, log only what you need, redact known-sensitive fields, and avoid enabling blanket query logging for long periods.

Use query inspection to answer a specific performance question

Seeing SQL is only the first step. For a slow query, copy a safely reconstructed query into the database client with representative values and inspect its execution plan; for an N+1 problem, count repeated query shapes; for an unexpected condition, inspect bindings. Different symptoms need different evidence, and dumping one builder does not automatically explain the request's database behavior.

Topics: Database Queries & Eloquent Debugging & Testing