Pagination — paginate(), simplePaginate(), cursorPaginate()
Concept
Laravel provides three pagination strategies with different SQL implementations and use cases.
paginate(int $perPage = 15): The standard paginator. Runs TWO queries: a COUNT(*) for total records, and a SELECT ... LIMIT ... OFFSET ... for the current page. Returns LengthAwarePaginator. Knows total pages, current page, can render numbered navigation. OFFSET degrades on large tables as the database must skip all preceding rows.
simplePaginate(int $perPage = 15): Runs ONE query: LIMIT $perPage + 1. If the extra row exists, there's a next page. No total count. Returns SimplePaginator. Only supports previous/next navigation. Faster than paginate() for large datasets.
cursorPaginate(int $perPage = 15): Uses an encrypted cursor (based on the last-seen row's key) rather than OFFSET. Runs ONE query: WHERE id > [cursor] LIMIT $perPage + 1. Returns CursorPaginator. Stable pagination (no rows shift if data changes between pages). No total count. Best performance on large tables. Requires orderBy on a unique, sequential column (usually id or created_at).
API pagination JSON output: All paginator instances implement Responsable — returning them from a controller produces a JSON response with items + pagination metadata. Eloquent's paginate() adds current_page, last_page, total, links, etc. to the JSON.
Links rendering (Blade): $paginator->links() renders HTML pagination links. Customizable with vendor:publish --tag=laravel-pagination.
Code Example
<?php
// Standard paginate — total count, full navigation
$users = DB::table('users')
->where('active', true)
->orderBy('name')
->paginate(15);
// SQL 1: SELECT COUNT(*) FROM users WHERE active = 1
// SQL 2: SELECT * FROM users WHERE active = 1 ORDER BY name LIMIT 15 OFFSET 0
$users->currentPage(); // 1
$users->lastPage(); // 67 (total pages)
$users->total(); // 1000 (total records)
$users->items(); // array of current page results
$users->perPage(); // 15
// Simple paginate — no total count, prev/next only
$users = User::where('active', true)->simplePaginate(20);
// SQL: SELECT * FROM users WHERE active = 1 LIMIT 21 OFFSET 0
$users->hasMorePages(); // true if next page exists
$users->nextPageUrl();
// Cursor paginate — fastest, stable, cursor-based
$users = User::where('active', true)
->orderBy('id') // REQUIRED: unique ordered column
->cursorPaginate(20);
// SQL: SELECT * FROM users WHERE active = 1 AND id > [cursor] LIMIT 21
// JSON API response — return paginator directly from controller
return response()->json(
User::with('profile')->paginate(15)
);
// {"current_page":1,"data":[...],"last_page":67,"total":1000,...}
// Appending query string params to pagination links
$users = User::paginate(15)->appends(['sort' => 'name', 'filter' => 'active']);
// Links: ?page=2&sort=name&filter=active
// Blade template
// @foreach ($users as $user)
// <tr><td>{{ $user->name }}</td></tr>
// @endforeach
// {{ $users->links() }} ← renders HTML navigation
// Custom per-page (from request, capped)
$perPage = min($request->integer('per_page', 15), 100);
$users = User::paginate($perPage);