Laravel Tutorial · Database and Eloquent · Lesson 19 of 27

Pagination

Split long lists into pages with paginate(), simplePaginate() and cursorPaginate(), and return JSON with page links for APIs.

Create a free account to save this page, take notes and track your progress. Sign in

Paginating queries

Call paginate(15) instead of get() and Laravel reads ?page= from the request, runs a count query plus a limited query, and returns a paginator:

  • In Blade, {{ $posts->links() }} renders page links.
  • Returned from an API route, it becomes JSON with data, current_page, last_page, total and links.

Variants:

Method Best for
paginate() Page numbers and totals (runs a COUNT query)
simplePaginate() Just "next/previous" (no COUNT, cheaper)
cursorPaginate() Infinite scroll and big tables: stable, no OFFSET, no duplicates when rows are added
Page numbers and cursor pagination
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;

Schema::create('products', function (Blueprint $t) { $t->id(); $t->string('name'); });

class Product extends Model
{
    public $timestamps = false;
    protected $guarded = [];
}

foreach (range(1, 7) as $i) {
    Product::create(['name' => "Product $i"]);
}

Route::get('/products', function () {
    $page = Product::orderBy('id')->paginate(3);
    return [
        'items' => $page->pluck('name'),
        'page' => $page->currentPage(),
        'last_page' => $page->lastPage(),
        'total' => $page->total(),
        'next' => $page->nextPageUrl(),
    ];
});
Route::get('/feed', fn () => Product::select('id', 'name')->orderBy('id')->cursorPaginate(3));   // the cursor encodes the last id

visit('GET', '/products');
visit('GET', '/products?page=3');
$feed = json_decode(visit('GET', '/feed')->getContent(), true);
$next = parse_url($feed['next_page_url'], PHP_URL_QUERY);
visit('GET', '/feed?'.$next);       // follow the cursor to the next page

Output

GET /products -> 200 {"items":["Product 1","Product 2","Product 3"],"page":1,"last_page":3,"total":7,"next":"http:\/\/localhost\/products?page=2"}
GET /products?page=3 -> 200 {"items":["Product 7"],"page":3,"last_page":3,"total":7,"next":null}
GET /feed -> 200 {"data":[{"id":1,"name":"Product 1"},{"id":2,"name":"Product 2"},{"id":3,"name":"Product 3"}],"path":"http:\/\/localhost\/feed","per_page":3,"next_cursor":"eyJpZCI6MywiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","next_page_url":"http:\/\/localhost\/feed?cursor=eyJpZCI6MywiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null,"prev_page_url":null}
GET /feed?cursor=eyJpZCI6MywiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ -> 200 {"data":[{"id":4,"name":"Product 4"},{"id":5,"name":"Product 5"},{"id":6,"name":"Product 6"}],"path":"http:\/\/localhost\/feed","per_page":3,"next_cursor":"eyJpZCI6NiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","next_page_url":"http:\/\/localhost\/feed?cursor=eyJpZCI6NiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":"eyJpZCI6NCwiX3BvaW50c1RvTmV4dEl0ZW1zIjpmYWxzZX0","prev_page_url":"http:\/\/localhost\/feed?cursor=eyJpZCI6NCwiX3BvaW50c1RvTmV4dEl0ZW1zIjpmYWxzZX0"}

Run this on your computer — see “Get Started” for setup.

Test yourself

Which pagination method avoids OFFSET and suits infinite scrolling on big tables?

  1. paginate()
  2. simplePaginate()
  3. cursorPaginate()
  4. chunk()
Show answer

Answer: cursorPaginate()

Was this page helpful?