Pagination
A thousand posts cannot go on one page. The list is split into pages, and Laravel counts the records, takes the right portion and draws the links by itself.
paginate
Call paginate() with the number of records per page instead of get():
public function index()
{
$posts = Post::latest()->paginate(15);
return view('posts.index', ['posts' => $posts]);
}Laravel takes the page number from the address: /posts?page=2.
Printing in a template
The result is looped over like a regular collection, and the links() method prints the page links:
@foreach ($posts as $post)
<article>
<h2>{{ $post->title }}</h2>
</article>
@endforeach
{{ $posts->links() }}Information about the pages
<p>
Showing records {{ $posts->firstItem() }}–{{ $posts->lastItem() }}
of {{ $posts->total() }}
</p>| Method | What it returns |
|---|---|
total() | The total number of records |
currentPage() | The current page number |
lastPage() | The last page number |
hasMorePages() | Whether there are more pages |
Keeping filters
If the list is filtered, the filter parameters must stay in the page links. withQueryString() does that:
$posts = Post::query()
->when($request->category, fn ($query, $category) => $query->where('category_id', $category))
->paginate(15)
->withQueryString();The link to the second page becomes /posts?category=3&page=2.
Simple pagination
paginate() runs two queries: one for the records and one for their total. On large tables the count is slow. simplePaginate() shows only "Previous" and "Next" and needs one query:
$posts = Post::latest()->simplePaginate(15);For an endless feed use cursorPaginate() — it is fast at any depth.
Styling the links
By default the links are marked up for Tailwind CSS. To change the markup, publish the templates into the project:
php artisan vendor:publish --tag=laravel-paginationThey appear in resources/views/vendor/pagination.
Pagination in an API
If the result of paginate() is returned from an API controller, it becomes JSON with the records and the page information:
{
"data": [{ "id": 1, "title": "First post" }],
"current_page": 1,
"last_page": 7,
"per_page": 15,
"total": 100
}Processing a large table
To go through every record in a background job, read them in chunks:
Post::where('is_published', true)->chunkById(500, function ($posts) {
foreach ($posts as $post) {
// processing
}
});Common mistake
Pagination without sorting gives unpredictable results: the database is not obliged to return rows in the same order, and a record may land on two pages or on none. Always set an order — latest(), orderBy('id').