Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Paginator Laravel Package

jasongrimes/paginator

A lightweight, framework-agnostic PHP pagination library that turns arrays or query results into page links. It calculates page ranges, handles edge cases, and renders customizable, accessible pagination markup—a simple drop-in for Laravel or any PHP app needing clean pagination.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is framework-agnostic, so there is no service provider or facade to register. Install it with Composer and instantiate it directly wherever you need pagination markup.

composer require jasongrimes/paginator

The core object takes four arguments and does exactly one job: it produces pagination chrome (the page numbers, ellipses, prev/next links). It does not fetch or slice your data — you still run your own skip()/take() query.

use JasonGrimes\Paginator;

$totalItems  = Post::count();
$perPage     = 15;
$currentPage = (int) request('page', 1);
$urlPattern  = route('posts.index', ['page' => '(:num)']); // e.g. /posts?page=(:num)

$paginator = new Paginator($totalItems, $perPage, $currentPage, $urlPattern);

return view('posts.index', [
    'posts'     => Post::latest()->forPage($currentPage, $perPage)->get(),
    'paginator' => $paginator,
]);

Where to look first: Paginator::getPages() — it returns the array of page/ellipsis/prev/next entries you can loop over — and Paginator::__toString(), which renders the bundled Stack Overflow–style template.

First use case: dropping {!! $paginator !!} into a Blade view instead of {{ $posts->links() }} when a designer wants the Flickr/Stack Overflow "1 2 3 … 47 48" layout with first and last pages always visible.

Implementation Patterns

1. Let Laravel paginate, let this package render

Avoid duplicating pagination math. Pull total/current/per-page from Laravel's paginator and feed them into this one:

$posts = Post::paginate(15);

$paginator = new Paginator(
    $posts->total(),
    $posts->perPage(),
    $posts->currentPage(),
    $posts->url(1) // returns a URL with page=1; swap in (:num) below
);

// Rebuild the pattern so the library can substitute page numbers
$urlPattern = $posts->url(1);
$paginator = new Paginator($posts->total(), $posts->perPage(), $posts->currentPage(),
    str_replace('page=1', 'page=(:num)', $urlPattern));

This gives you Laravel's query handling plus this package's markup.

2. Render from getPages() for full control

For clean Blade output (with proper escaping), skip __toString() and loop the page objects:

@if ($paginator->getTotalPages() > 1)
<nav aria-label="Pagination">
    <ul class="pagination">
        @foreach ($paginator->getPages() as $page)
            @if ($page->isDisabled)
                <li class="disabled"><span>{!! $page !!}</span></li>
            @elseif ($page->isCurrent)
                <li class="active"><span>{{ $page->num }}</span></li>
            @else
                <li><a href="{{ $page->url }}">{{ $page->num }}</a></li>
            @endif
        @endforeach
    </ul>
</nav>
@endif

Each page object exposes num, url, isCurrent and isDisabled, and its __toString() outputs the inner label («, », …, or the number).

3. Customize the bundled template

If you want to keep {!! $paginator !!} but match Tailwind or Bootstrap 5, override the template once via a small view composer or a base controller:

$paginator->setTemplate(<<<'HTML'
<ul class="flex gap-1">
    <li>{{prev}}</li>
    {{pages}}
    <li>{{next}}</li>
</ul>
HTML);

The package's placeholders are {{pages}}, {{prev}}, {{next}}, {{first}}, {{last}}, {{current}}, {{total}}. Do not feed it a compiled Blade view here — the {{ }} syntax will collide.

4. Centralize in a view composer

Since every list screen needs this, register a composer that injects a configured paginator:

View::composer('partials.pager', function ($view) {
    $view->with('pager', new Paginator(
        request()->attributes->get('total', 0),
        request()->attributes->get('per_page', 15),
        (int) request('page', 1),
        request()->fullUrlWithQuery(['page' => '(:num)'])
    ));
});

fullUrlWithQuery is the cleanest way to preserve existing filters/sorts in the URL pattern.

5. Use it for non-Eloquent sources

Any countable list works — API result envelopes, log files, array keys:

$paginator = new Paginator(count($results), 25, $page, '/logs?page=(:num)');
$slice = array_slice($results, ($page - 1) * 25, 25);

Gotchas and Tips

  • It paginates nothing. The name is misleading; you are responsible for forPage()/array_slice(). Only the links are generated.
  • (:num) is mandatory in the URL pattern. If the placeholder is missing, every link resolves to the same page. When the current page is 1, the library often substitutes the base URL, so test the boundary.
  • __toString() emits raw HTML. In Blade you must use {!! $paginator !!}. Better: loop getPages() and escape $page->url yourself, as shown above.
  • Query strings are not preserved automatically. Build the pattern with fullUrlWithQuery(['page' => '(:num)']); otherwise filters vanish after page 1.
  • Ellipsis entries are not page numbers. getPages() includes … items whose num is '...' and whose url is null. Guard against linking them ($page->isDisabled is not always true for these — check $page->num or $page->url === null).
  • Out-of-range pages are clamped internally. Requesting page 999 gives you valid "current" state, but your query still needs its own bounds check or you'll render an empty table under valid-looking links.
  • Don't mix the template engine with Blade. setTemplate() uses {{placeholder}} substitution; rendering that through Blade::render() will either throw or mangle output.
  • Old package, thin maintenance surface (last release 2018). It is small and dependency-free, so it runs fine on modern PHP, but pin it and don't expect Laravel-11-specific fixes. If you need Tailwind, accessibility attributes, or aria-current, the getPages() loop pattern is more future-proof than the bundled template.
  • Config quirks: there is no config file. Everything is constructor args plus setTemplate(). The default class names are Bootstrap 3–era (pagination, active, disabled).
  • Extension point: the paginator accepts custom page-rendering via template placeholders; if you need truly custom output, wrap getPages() in your own presenter class rather than subclassing.
  • Order matters with str_replace URL rewrites. When adapting Laravel's url(1), replace only the page segment; a naive str_replace('1', '(:num)') will corrupt IDs or slugs elsewhere in the URL.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core