Laravel Reference
Getting started
The DB facade gives you a fluent interface for building and running queries against any configured database connection, without writing raw SQL by hand. Every method returns the query builder itself (except the terminal methods like get()), so calls chain together. Eloquent is built on top of this same query builder, adding models and relationships — reach for the builder directly when a query doesn't map cleanly to a single model.
use Illuminate\Support\Facades\DB;
$users = DB::table('users')->get();
$user = DB::table('users')->where('id', 1)->first();
$name = DB::table('users')->where('id', 1)->value('name');
$exists = DB::table('users')->where('email', $email)->exists();
Select clauses
DB::table('users')->select('id', 'name', 'email')->get();
DB::table('users')->select('name as full_name')->get();
DB::table('users')->distinct()->pluck('country');
Where clauses
The most common way to filter results. Chain multiple where() calls to combine them with AND; use orWhere() for OR.
DB::table('users')
->where('active', true)
->where('age', '>=', 18)
->orWhere('role', 'admin')
->get();
DB::table('users')->whereIn('id', [1, 2, 3])->get();
DB::table('users')->whereNotIn('id', [1, 2, 3])->get();
DB::table('users')->whereBetween('age', [18, 30])->get();
DB::table('users')->whereNull('deleted_at')->get();
DB::table('users')->whereNotNull('email_verified_at')->get();
DB::table('posts')->whereDate('created_at', '2026-01-01')->get();
// Group conditions with a closure to control operator precedence
DB::table('users')->where(function ($query) {
$query->where('role', 'admin')
->orWhere('role', 'manager');
})->where('active', true)->get();
// Subqueries — filter by a related table without a join
DB::table('users')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('orders')
->whereColumn('orders.user_id', 'users.id');
})
->get();
DB::table('users')
->whereIn('id', DB::table('orders')->select('user_id')->where('total', '>', 100))
->get();
// Only add a condition when it's actually needed — no if/else branching the query
DB::table('users')
->when($request->filled('role'), function ($query) use ($request) {
$query->where('role', $request->input('role'));
})
->get();
when() is the cleanest way to build a query from optional filter/search input — the closure only runs when the first argument is truthy, so there's no need to conditionally reassign the query variable across if/else branches.
Ordering, grouping, and limiting
DB::table('users')->orderBy('name')->get();
DB::table('users')->orderByDesc('created_at')->get();
DB::table('users')->latest()->first(); // orderBy('created_at', 'desc')
DB::table('users')->oldest()->first();
DB::table('orders')
->select('customer_id', DB::raw('SUM(total) as total_spent'))
->groupBy('customer_id')
->having('total_spent', '>', 1000)
->get();
DB::table('users')->skip(10)->take(5)->get();
DB::table('users')->paginate(15);
Results from get() come back as a Collection, so they're chainable with map, filter, and the rest immediately.
Joins
DB::table('users')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.name', 'orders.total')
->get();
DB::table('users')
->leftJoin('orders', 'users.id', '=', 'orders.user_id')
->get();
Aggregates
DB::table('orders')->count();
DB::table('orders')->max('total');
DB::table('orders')->min('total');
DB::table('orders')->avg('total');
DB::table('orders')->sum('total');
Inserts, updates, and deletes
DB::table('users')->insert([
'name' => 'Jane Doe',
'email' => 'jane@example.com',
]);
$id = DB::table('users')->insertGetId([
'name' => 'Jane Doe',
]);
DB::table('users')->where('id', 1)->update(['votes' => 1]);
DB::table('users')->where('votes', '<', 100)->increment('votes', 5);
DB::table('users')->where('id', 1)->delete();
// Insert new rows, or update the given columns where a unique/primary key already matches
DB::table('users')->upsert(
[
['email' => 'jane@example.com', 'name' => 'Jane Doe'],
['email' => 'john@example.com', 'name' => 'John Doe'],
],
uniqueBy: ['email'],
update: ['name'],
);
upsert() needs a unique or primary key on the given column(s) at the database level to work — it compiles to a single INSERT ... ON DUPLICATE KEY UPDATE (or the Postgres/SQLite equivalent) rather than one query per row.
Chunking large result sets
get() loads every matching row into memory at once. For a big table, process it in batches instead:
DB::table('users')->orderBy('id')->chunk(200, function ($users) {
foreach ($users as $user) {
// ...
}
});
// Safer when the callback updates/deletes rows in the chunk being iterated
DB::table('users')->chunkById(200, function ($users) {
foreach ($users as $user) {
DB::table('users')->where('id', $user->id)->update(['processed' => true]);
}
});
Transactions
Wrap a group of writes that must all succeed or all fail together — if the closure throws, every query inside it is automatically rolled back.
DB::transaction(function () {
DB::table('accounts')->where('id', 1)->decrement('balance', 100);
DB::table('accounts')->where('id', 2)->increment('balance', 100);
});
// Manual control when you need to decide whether to commit based on more than an exception
DB::beginTransaction();
try {
// ...
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
}
Prefer the closure form — it also retries automatically on a deadlock if you pass a second argument, e.g. DB::transaction($callback, 3).
Raw expressions
Drop down to raw SQL fragments when the fluent methods don't cover what you need — but prefer bindings (? placeholders) over string-interpolating user input to avoid SQL injection.
DB::select('select * from users where active = ?', [true]);
DB::table('users')
->whereRaw('age > ? and active = ?', [18, true])
->get();