Saltar al contenido principal

¿Qué es la paginación?

La paginación de Laravel se integra con el query builder y Eloquent ORM, y se puede usar sin configuración. La página actual se obtiene automáticamente del parámetro page de la solicitud HTTP y se añade también a los enlaces generados. El HTML por defecto es compatible con Tailwind CSS, y también hay opción para Bootstrap CSS.

Tres tipos de paginación

MétodoDevuelveCaracterísticas
paginate()LengthAwarePaginatorCuenta el total. Genera enlaces con número de página
simplePaginate()PaginatorNo cuenta el total. Solo “anterior/siguiente”
cursorPaginate()CursorPaginatorBasada en cursor. Ideal para grandes volúmenes

Uso básico

Paginación con query builder

use Illuminate\Support\Facades\DB;

// 15 resultados por página
$users = DB::table('users')->paginate(15);

Paginación con Eloquent

use App\Models\User;

$users = User::paginate(15);

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

simplePaginate

Cuando no necesitas la consulta de conteo total (solo “anterior/siguiente”), simplePaginate() es más eficiente.
$users = DB::table('users')->simplePaginate(15);
$users = User::where('active', true)->simplePaginate(15);
Si no necesitas mostrar “X de N en total”, elige simplePaginate(). paginate() ejecuta un COUNT(*) adicional, por lo que simplePaginate() es más rápido.

cursorPaginate (paginación por cursor)

En lugar de OFFSET usa WHERE, por lo que ofrece un gran rendimiento con muchos datos. Es especialmente adecuado para interfaces de scroll infinito.
// orderBy es obligatorio
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
$users = User::where('active', true)->cursorPaginate(15);
Las URLs contienen un cursor en lugar de un número de página.
http://example.com/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
Para usar cursorPaginate se necesita orderBy. Además, la columna de ordenación debe pertenecer a la tabla paginada.

OFFSET vs. cursor

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

-- cursorPaginate() — usa WHERE (aprovecha los índices)
SELECT * FROM users WHERE id > 15 ORDER BY id ASC LIMIT 15;
La paginación por cursor aprovecha los índices y evita mejor la duplicación/omisión de registros cuando hay muchas inserciones/eliminaciones. Sin embargo, no genera enlaces por número de página, solo “anterior/siguiente”.

Implementación en el controlador

<?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'));
    }
}

Mostrar enlaces de paginación en Blade

<div class="container">
    @foreach ($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach
</div>

{{-- Muestra los enlaces (compatibles con Tailwind CSS) --}}
{{ $users->links() }}
links() genera automáticamente el HTML de los enlaces. Muestra tres páginas a cada lado de la actual.

Ajustar el número de enlaces

Con onEachSide() cambias cuántas páginas se muestran a cada lado.
{{-- Muestra 5 páginas a cada lado --}}
{{ $users->onEachSide(5)->links() }}

Recibir el tamaño de página desde la solicitud

public function index(Request $request): View
{
    $perPage = $request->integer('per_page', 15);
    $perPage = min(max($perPage, 1), 100); // Limitar entre 1 y 100

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

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

Múltiples paginadores en una misma página

Si hay dos paginadores en una misma pantalla, ambos usan page y entran en conflicto. Cambia el nombre del parámetro con el tercer argumento.
$users = User::paginate(
    perPage: 15,
    columns: ['*'],
    pageName: 'users'
);

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

Personalización de la URL

Cambiar la base de la URL

$users = User::paginate(15);

// URLs del tipo /admin/users?page=N
$users->withPath('/admin/users');

Añadir parámetros de query

// Añade sort=votes a cada enlace
$users->appends(['sort' => 'votes']);

// Hereda todos los parámetros de la solicitud actual
$users->withQueryString();

Añadir fragmento hash

// Añade #users al final de la URL
$users->fragment('users');

Respuesta API (JSON)

Al devolver un paginador directamente desde una ruta o controlador, se convierte automáticamente a JSON.
use App\Models\User;

Route::get('/api/users', function () {
    return User::paginate(15);
});
Formato de la respuesta JSON:
{
    "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": "Taro Yamada" },
        { "id": 2, "name": "Hanako Suzuki" }
    ]
}

Combinación con API Resources

Para envolver el resultado de paginate() en una colección de recurso, pásaselo 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);
});
Al pasar el paginador a UserResource::collection(), la información de paginación se añade automáticamente como metadatos.
El JSON de cursorPaginate() incluye next_cursor y prev_cursor en lugar de números de página. El cliente API usa esos valores como parámetro cursor en la siguiente solicitud.

Vistas de paginación personalizadas

Indicar la vista directamente

{{-- Muestra los enlaces con una vista personalizada --}}
{{ $paginator->links('vendor.pagination.custom') }}

{{-- Pasar datos adicionales --}}
{{ $paginator->links('vendor.pagination.custom', ['theme' => 'dark']) }}

Cambiar la vista por defecto

Primero publica las vistas oficiales para personalizarlas.
php artisan vendor:publish --tag=laravel-pagination
Se crean en resources/views/vendor/pagination/:
  • tailwind.blade.php — Por defecto (Tailwind CSS)
  • bootstrap-5.blade.php — Bootstrap 5
  • simple-tailwind.blade.php — Para simplePaginate
Puedes editar tailwind.blade.php directamente o crear una nueva vista e indicarla en AppServiceProvider.
use Illuminate\Pagination\Paginator;

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

Usar Bootstrap CSS

Para usar Bootstrap en lugar de Tailwind, indícalo en el boot() de AppServiceProvider.
use Illuminate\Pagination\Paginator;

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

Crear un paginador manualmente

Para paginar datos existentes (por ejemplo, un array), instancia directamente la clase.
use Illuminate\Pagination\LengthAwarePaginator;

$items = collect(range(1, 200))->map(fn ($i) => ['id' => $i, 'name' => "Elemento {$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()]
);

Métodos de instancia habituales

$paginator = User::paginate(15);

$paginator->currentPage();     // Página actual
$paginator->lastPage();        // Última página (no en simplePaginate)
$paginator->total();           // Total (no en simplePaginate)
$paginator->perPage();         // Elementos por página
$paginator->count();           // Elementos en la página actual
$paginator->firstItem();       // Índice del primer elemento
$paginator->lastItem();        // Índice del último elemento
$paginator->hasPages();        // ¿Hay más de una página?
$paginator->hasMorePages();    // ¿Hay página siguiente?
$paginator->onFirstPage();     // ¿Estamos en la primera?
$paginator->onLastPage();      // ¿Estamos en la última?
$paginator->nextPageUrl();     // URL de la siguiente
$paginator->previousPageUrl(); // URL de la anterior
$paginator->url(3);            // URL de la página 3
$paginator->items();           // Array de elementos actuales

Resumen

  • paginate() — cuando necesitas total y enlaces numerados (pantallas de listado habituales)
  • simplePaginate() — cuando basta con “anterior/siguiente” (más rápido)
  • cursorPaginate() — para grandes volúmenes, scroll infinito o mucha escritura (máximo rendimiento)
@foreach ($users as $user)
    <p>{{ $user->name }}</p>
@endforeach

{{ $users->links() }}
Pasa el resultado de paginate() a la vista y muestra los enlaces con links(). La página actual se detecta automáticamente desde el parámetro page.
Devolver el paginador desde la ruta lo convierte automáticamente a JSON. Para combinarlo con API Resources, devuelve UserResource::collection($paginator). La respuesta contiene data (registros) y varios campos de metadatos.
Última modificación el 13 de julio de 2026