Model::paginate

Returns one page of results plus the totals needed to render numbered pagination links.

Signature
Model::paginate(Closure|int|null $perPage = null, array|string $columns = ['*'], string $pageName = 'page', int|null $page = null, Closure|int|null $total = null): Illuminate\Pagination\LengthAwarePaginator

Parameters

ParameterTypeDescription
$$perPageoptionalClosure|int|null= nullRows per page. When null, the model's $perPage property is used, which defaults to 15. A closure receives the total and returns the page size.
$$columnsoptionalarray|string= ['*']Columns to select for the page query.
$$pageNameoptionalstring= 'page'Query string parameter the current page is read from and written to. Change it when two paginators appear on one screen.
$$pageoptionalint|null= nullPage number to render. When null it is resolved from the current request via Paginator::resolveCurrentPage().
$$totaloptionalClosure|int|null= nullTotal row count. Supply it to skip the extra count query, for example from a cached value.

Return value

Illuminate\Pagination\LengthAwarePaginatorA length-aware paginator holding the current page of models plus the total count, page number and generated URLs.

Errors

Throws InvalidArgumentException when the resolved page number is not a positive integer.

Changelog

  • PHP 12.1.0The declared return type of paginate() was narrowed to Illuminate\Pagination\LengthAwarePaginator.

paginate works out the LIMIT and OFFSET for you from the current page, runs a second query to count the total, and wraps both in a LengthAwarePaginator. The paginator is iterable, so a Blade @foreach over it works exactly like a collection, and {{ $users->links() }} renders the numbered links.

When to reach for it

Use it for any list a human scrolls through and where the UI shows page numbers or a total. Returning a paginator straight from a controller produces a JSON envelope with data, total, current_page and the page URLs, which is why it is the default shape for index endpoints.

What trips people up

The page number comes from the request, not from your code — ?page=3 is read automatically. Outside an HTTP request, in a job or a command, no page is resolved and you always get page one unless you pass the fourth argument explicitly.

Two paginators on a single screen both read page and therefore move together. Pass a distinct $pageName to each.

The count query is not free. If you only need next and previous links, simplePaginate skips it; for large or fast-moving tables cursorPaginate avoids OFFSET scans entirely.

Example

Example · php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
    public function index(): View
    {
        $users = User::where('votes', '>', 100)->paginate(15);

        echo $users->total();        // 50
        echo $users->perPage();      // 15
        echo $users->currentPage();  // 1 (or whatever ?page= says)
        echo $users->lastPage();     // 4
        echo $users->count();        // 15 — items on this page
        echo $users->nextPageUrl();  // http://laravel.app?page=2

        return view('user.index', ['users' => $users]);
    }
}

When to use it

  • Render a paginated table of users with numbered page links in Blade.
  • Return a JSON index endpoint that includes total and current_page metadata.
  • Show two independently paged lists on the same admin screen.
  • Skip the count query on a huge table by supplying a cached total.

Frequently asked questions

How does Laravel know which page to show?

paginate() reads the page query string parameter from the current request through Paginator::resolveCurrentPage(). You can override it by passing the page number as the fourth argument, which is required outside an HTTP request.

Why do both paginators change together on one page?

They are both reading and writing the same page parameter. Pass a distinct third argument, the $pageName, to each call so one uses ?users=2 and the other ?posts=2.

How do I avoid the extra count query from paginate()?

Use simplePaginate() when next and previous links are enough, or pass a known total as the fifth argument. For large tables, cursorPaginate() avoids both the count and the growing OFFSET scan.

More examples

Two paginators on one screen

The third argument controls both the parameter the paginator reads and the one it writes into its links, so clicking through one list no longer resets the other. withQueryString() carries the rest of the current query string, which is what keeps sort and filter selections alive across page clicks.

Example · php
<?php

use App\Models\Post;
use App\Models\User;

// Both read ?page= and would move together.
$users = User::paginate(15);
$posts = Post::paginate(15);

// Give each its own query string parameter.
$users = User::paginate($perPage = 15, $columns = ['*'], $pageName = 'users');
$posts = Post::paginate($perPage = 10, $columns = ['*'], $pageName = 'posts');

echo $users->getPageName();  // users
echo $posts->getPageName();  // posts
echo $users->nextPageUrl();  // http://laravel.app?users=2

// Keep existing filters on the links:
$users = User::paginate(15)->withQueryString();

Paginating outside an HTTP request

There is no request in a console command, so Paginator::resolveCurrentPage() falls back to 1 every time. Passing $page explicitly is what makes the loop advance — though for a pure export, chunkById is both cheaper and safer since it avoids the repeated count query and the growing OFFSET.

Example · php
<?php

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;

class ExportUsers extends Command
{
    public function handle(): void
    {
        $page = 1;

        do {
            // Without the 4th argument this would loop on page 1 forever.
            $users = User::orderBy('id')->paginate(100, ['*'], 'page', $page);

            foreach ($users as $user) {
                $this->line($user->email);
            }

            $page++;
        } while ($users->hasMorePages());
    }
}

Choosing between the three paginators

paginate needs total() and lastPage(), and pays for them with an extra count query on every request. simplePaginate drops both and issues one query. cursorPaginate replaces OFFSET with a where clause on the ordered column, so page 500 costs the same as page 1 — but it cannot produce numbered links.

Example · php
<?php

use App\Models\User;

// Numbered links. Runs a count query. LengthAwarePaginator.
$a = User::paginate(15);
echo $a->total();      // 5000
echo $a->lastPage();   // 334

// Next/Previous only. No count query. Paginator.
$b = User::simplePaginate(15);
echo $b->hasMorePages() ? 'more' : 'end';   // more

// Keyset pagination. Requires an order by. CursorPaginator.
$c = User::orderBy('id')->cursorPaginate(15);
echo $c->nextPageUrl();  // http://localhost/users?cursor=eyJpZCI6MTUs...

// Skip the count when you already know the total.
$d = User::paginate(perPage: 15, total: 5000);
echo $d->lastPage();   // 334

Discussion

  • Be the first to comment on this lesson.