A common bug: a filtered or sorted list works fine on page 1, but clicking "page 2" silently drops the filter — because Laravel's paginator only carries its own page parameter in the links it generates, not the rest of the query string, unless you tell it to.
The problem
// URL: /products?category=tools&sort=price
$products = Product::where('category', request('category'))
->paginate(20);
The pagination links render as /products?page=2 — category and sort are gone, and page 2 shows the unfiltered list.
Fix 1: withQueryString() — carry everything
{{ $products->withQueryString()->links() }}
This appends every current query parameter to every pagination link automatically. It's the right default for most filtered listing pages, and as of Laravel 8+ it's often applied automatically when you call paginate() inside a request that already has query parameters — but it's still worth calling explicitly, since relying on the implicit behavior makes it easy to lose track of when it does and doesn't apply.
Fix 2: appends() — carry specific parameters only
{{ $products->appends([
'category' => request('category'),
'sort' => request('sort'),
])->links() }}
Use this when you want control over exactly which parameters survive pagination — for example, deliberately dropping a one-time "just added" flag while keeping the real filters.
Reading params back on the next page
Carrying the query string in the links only helps if the controller also reads it back on every request, not just the first one:
$products = Product::query()
->when(request('category'), fn ($q, $category) => $q->where('category', $category))
->when(request('sort'), fn ($q, $sort) => $q->orderBy($sort))
->paginate(20)
->withQueryString();
The when() clauses make the filters conditional so an empty request still returns the full unfiltered list, and each subsequent page request re-applies the same filters from its own query string.