Vai al contenuto principale

Cos’è la paginazione

La paginazione di Laravel è integrata con Query Builder ed Eloquent ORM e si può usare senza configurazioni. La pagina corrente viene letta automaticamente dal parametro di query page della richiesta HTTP e aggiunta automaticamente ai link generati. L’HTML predefinito è compatibile con Tailwind CSS; puoi scegliere anche Bootstrap CSS.

Tre tipi di paginazione

MetodoRitornoCaratteristiche
paginate()LengthAwarePaginatorRecupera il totale. Genera link ai numeri di pagina
simplePaginate()PaginatorNon recupera il totale. Solo “precedente” e “successivo”
cursorPaginate()CursorPaginatorBasato su cursore. Ideale per grandi volumi di dati

Uso di base

Paginazione con Query Builder

use Illuminate\Support\Facades\DB;

// Recupera 15 elementi per pagina
$users = DB::table('users')->paginate(15);

Paginazione con Eloquent

use App\Models\User;

$users = User::paginate(15);

// Con condizioni
$users = User::where('votes', '>', 100)->paginate(15);

simplePaginate

Quando non serve la query di conteggio totale (mostri solo i link “precedente” e “successivo”), è più efficiente usare simplePaginate().
$users = DB::table('users')->simplePaginate(15);
$users = User::where('active', true)->simplePaginate(15);
Se non ti serve mostrare “elemento N di M totali”, scegli simplePaginate(). paginate() esegue una query COUNT(*) aggiuntiva, quindi simplePaginate() è più veloce.

cursorPaginate (paginazione a cursore)

La paginazione a cursore usa una clausola WHERE al posto di OFFSET, offrendo prestazioni elevate su grandi volumi di dati. Particolarmente adatta a UI a scroll infinito.
// orderBy è obbligatorio
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
$users = User::where('active', true)->cursorPaginate(15);
Gli URL generati contengono una stringa di cursore invece del numero di pagina.
http://example.com/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
Per usare la paginazione a cursore è obbligatorio orderBy. Inoltre, la colonna di ordinamento deve appartenere alla tabella oggetto della paginazione.

Confronto tra OFFSET e cursore

-- paginate() / simplePaginate() — usa OFFSET
SELECT * FROM users ORDER BY id ASC LIMIT 15 OFFSET 15;

-- cursorPaginate() — usa una clausola WHERE (l'indice viene sfruttato)
SELECT * FROM users WHERE id > 15 ORDER BY id ASC LIMIT 15;
La paginazione a cursore sfrutta gli indici in modo efficace e riduce il rischio di duplicazioni o omissioni di record anche in presenza di inserimenti e cancellazioni frequenti. Non genera però link ai singoli numeri di pagina: solo “precedente” e “successivo”.

Implementazione nel controller

<?php

namespace App\Http\Controllers;

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

class UserController extends Controller
{
    public function index(): View
    {
        $users = User::orderBy('name')->paginate(20);

        return view('users.index', compact('users'));
    }
}
<div class="container">
    @foreach ($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach
</div>

{{-- Emette i link di paginazione (compatibili con Tailwind CSS) --}}
{{ $users->links() }}
Il metodo links() genera automaticamente l’HTML dei link. Vengono mostrati i link alle 3 pagine prima e dopo la pagina corrente. Con onEachSide() puoi cambiare il numero di link mostrati prima e dopo la pagina corrente.
{{-- Mostra 5 pagine prima e 5 dopo quella corrente --}}
{{ $users->onEachSide(5)->links() }}

Ricevere dalla richiesta il numero di elementi per pagina

public function index(Request $request): View
{
    $perPage = $request->integer('per_page', 15);
    $perPage = min(max($perPage, 1), 100); // Limita nell'intervallo 1–100

    $users = User::paginate($perPage);

    return view('users.index', compact('users'));
}

Mostrare più paginator nella stessa pagina

Se hai due paginator sulla stessa schermata, entrambi usano il parametro page andando in conflitto. Cambia il nome del parametro con il terzo argomento.
$users = User::paginate(
    perPage: 15,
    columns: ['*'],
    pageName: 'users'
);

$posts = Post::paginate(
    perPage: 10,
    columns: ['*'],
    pageName: 'posts'
);

Personalizzazione degli URL

Cambiare l’URL base

$users = User::paginate(15);

// Genera URL nel formato /admin/users?page=N
$users->withPath('/admin/users');

Aggiungere parametri di query

// Aggiunge sort=votes a ogni link di pagina
$users->appends(['sort' => 'votes']);

// Eredita tutti i parametri di query della richiesta corrente
$users->withQueryString();

Aggiungere un frammento hash

// Aggiunge #users in fondo agli URL
$users->fragment('users');

Risposta API (output JSON)

Restituendo il paginator direttamente da una route o controller, viene convertito automaticamente in JSON.
use App\Models\User;

Route::get('/api/users', function () {
    return User::paginate(15);
});
Formato JSON della risposta:
{
    "total": 50,
    "per_page": 15,
    "current_page": 1,
    "last_page": 4,
    "first_page_url": "http://example.com/api/users?page=1",
    "last_page_url": "http://example.com/api/users?page=4",
    "next_page_url": "http://example.com/api/users?page=2",
    "prev_page_url": null,
    "path": "http://example.com/api/users",
    "from": 1,
    "to": 15,
    "data": [
        { "id": 1, "name": "Mario Rossi" },
        { "id": 2, "name": "Giulia Bianchi" }
    ]
}

Combinazione con le API resource

Per incapsulare il risultato di paginate() in una API resource collection, passalo a UserResource::collection().
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/api/users', function () {
    $users = User::paginate(15);

    return UserResource::collection($users);
});
Passando il paginator a UserResource::collection(), le informazioni di paginazione vengono aggiunte automaticamente come metadati.
Nel JSON di cursorPaginate() non ci sono numeri di pagina ma next_cursor e prev_cursor. Il client API usa questi valori come parametro cursor della richiesta successiva.

Viste di paginazione personalizzate

Specificare direttamente il file della vista

{{-- Emette i link usando una vista personalizzata --}}
{{ $paginator->links('vendor.pagination.custom') }}

{{-- Passa dati aggiuntivi --}}
{{ $paginator->links('vendor.pagination.custom', ['theme' => 'dark']) }}

Cambiare la vista predefinita con un file personalizzato

Pubblica prima le viste ufficiali e poi personalizzale.
php artisan vendor:publish --tag=laravel-pagination
In resources/views/vendor/pagination/ vengono generati i seguenti file:
  • tailwind.blade.php — predefinito (per Tailwind CSS)
  • bootstrap-5.blade.php — per Bootstrap 5
  • simple-tailwind.blade.php — per simplePaginate
Modifica direttamente tailwind.blade.php oppure crea una nuova vista e indicala in AppServiceProvider.
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    Paginator::defaultView('vendor.pagination.custom');
    Paginator::defaultSimpleView('vendor.pagination.simple-custom');
}

Usare Bootstrap CSS

Se al posto di Tailwind usi Bootstrap, configuralo in boot() di AppServiceProvider.
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    Paginator::useBootstrapFive(); // Bootstrap 5
    // Paginator::useBootstrapFour(); // Bootstrap 4
}

Creazione manuale di un paginator

Se vuoi paginare dati esistenti come un array, istanzia direttamente la classe paginator.
use Illuminate\Pagination\LengthAwarePaginator;

$items = collect(range(1, 200))->map(fn ($i) => ['id' => $i, 'name' => "Item {$i}"]);

$perPage = 15;
$currentPage = request()->integer('page', 1);

$paginator = new LengthAwarePaginator(
    items: $items->forPage($currentPage, $perPage),
    total: $items->count(),
    perPage: $perPage,
    currentPage: $currentPage,
    options: ['path' => request()->url()]
);

Metodi d’istanza comuni

$paginator = User::paginate(15);

$paginator->currentPage();     // Numero di pagina corrente
$paginator->lastPage();        // Numero dell'ultima pagina (non disponibile con simplePaginate)
$paginator->total();           // Totale (non disponibile con simplePaginate)
$paginator->perPage();         // Elementi per pagina
$paginator->count();           // Elementi nella pagina corrente
$paginator->firstItem();       // Indice del primo elemento della pagina corrente
$paginator->lastItem();        // Indice dell'ultimo elemento della pagina corrente
$paginator->hasPages();        // Se esistono più pagine
$paginator->hasMorePages();    // Se esiste una pagina successiva
$paginator->onFirstPage();     // Se è la prima pagina
$paginator->onLastPage();      // Se è l'ultima pagina
$paginator->nextPageUrl();     // URL della pagina successiva
$paginator->previousPageUrl(); // URL della pagina precedente
$paginator->url(3);            // URL della pagina 3
$paginator->items();           // Array degli elementi della pagina corrente
  • paginate() — Quando servono totale e link ai numeri di pagina (schermate a lista comuni)
  • simplePaginate() — Quando bastano i soli link “precedente” e “successivo” (più veloce)
  • cursorPaginate() — Con grandi volumi di dati, scroll infinito o scritture frequenti (massime prestazioni)
@foreach ($users as $user)
    <p>{{ $user->name }}</p>
@endforeach

{{ $users->links() }}
Passa il risultato di paginate() alla vista e usa links() per emettere i link alle pagine. La pagina corrente viene rilevata automaticamente dal parametro di query page.
Restituendo direttamente il paginator dalla route, viene convertito automaticamente in JSON. Per combinarlo con le API resource restituisci UserResource::collection($paginator). La risposta contiene data (array di record) e vari metadati.
Ultima modifica il 13 luglio 2026