php 45 lines · 9 steps

Building a filtered product index in Laravel

How Eloquent's when() method conditionally chains query filters from validated request input.

Explained by highlit
1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Models\Product;
6use Illuminate\Http\Request;
7 
8class ProductController extends Controller
9{
10 public function index(Request $request)
11 {
12 $filters = $request->validate([
13 'search' => ['nullable', 'string', 'max:255'],
14 'category_id' => ['nullable', 'integer', 'exists:categories,id'],
15 'min_price' => ['nullable', 'numeric', 'min:0'],
16 'max_price' => ['nullable', 'numeric', 'gte:min_price'],
17 'in_stock' => ['nullable', 'boolean'],
18 'sort' => ['nullable', 'in:newest,price_asc,price_desc'],
19 ]);
20 
21 $products = Product::query()
22 ->with('category')
23 ->when($filters['search'] ?? null, function ($query, $search) {
24 $query->where(function ($q) use ($search) {
25 $q->where('name', 'like', "%{$search}%")
26 ->orWhere('sku', 'like', "%{$search}%");
27 });
28 })
29 ->when($filters['category_id'] ?? null, fn ($query, $id) => $query->where('category_id', $id))
30 ->when($filters['min_price'] ?? null, fn ($query, $min) => $query->where('price', '>=', $min))
31 ->when($filters['max_price'] ?? null, fn ($query, $max) => $query->where('price', '<=', $max))
32 ->when($request->boolean('in_stock'), fn ($query) => $query->where('stock', '>', 0))
33 ->when($filters['sort'] ?? 'newest', function ($query, $sort) {
34 match ($sort) {
35 'price_asc' => $query->orderBy('price'),
36 'price_desc' => $query->orderByDesc('price'),
37 default => $query->latest(),
38 };
39 })
40 ->paginate(24)
41 ->withQueryString();
42 
43 return view('products.index', compact('products', 'filters'));
44 }
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Validating request input up front turns untrusted query parameters into a safe, typed array you can trust downstream.
  2. 2when() lets you assemble a query conditionally without a tangle of if statements, applying each filter only when its value is present.
  3. 3Pairing paginate() with withQueryString() keeps active filters intact as users move between pages.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a filtered product index in Laravel — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code