kgilden/pager
kgilden/pager is a lightweight pagination package for Laravel/PHP applications. It helps split long result sets into pages and render simple pager links, making it easy to browse lists, search results, and other query output with minimal setup.
Start by installing the package and confirming its public API before wiring it into a controller:
composer require kgilden/pager
The package is intentionally thin, so the first places to look are:
README.md for the documented pager constructor/factory and link-rendering method.composer.json for the namespace, Laravel auto-discovery, and supported Laravel/PHP versions.src/ for the pager class and any link/markup helpers.resources/views/ or equivalent for the default pagination markup, if published views are supported.A minimal first use case is an admin table backed by an array or already-fetched result set. The package handles page/offset math; you still own data retrieval and rendering:
use Kgilden\Pager\Pager; // confirm exact namespace in composer.json
$page = max(1, (int) $request->query('page', 1));
$perPage = 25;
$total = count($rows);
$pager = new Pager($total, $perPage, $page); // adapt to documented signature
$pageRows = array_slice($rows, $pager->offset(), $perPage);
Then render the links with the package’s documented helper or view:
{{ $pager->links() }}
If the package exposes a factory or helper instead of a constructor, use that form. The important integration point is the same: feed it total, perPage, and the current page, then use its offset and link output.
Array and collection lists. For in-memory data, use the pager only for offset calculation and slice manually:
$pageRows = array_slice($rows, $pager->offset(), $pager->perPage());
// Collections
$pageRows = $collection->slice($pager->offset(), $pager->perPage())->values();
This is the safest fit for reports, config-driven lists, and small datasets where loading everything is acceptable.
Eloquent and query builder results. The pager does not replace Laravel’s query paginator, so apply its offset and limit yourself:
$total = (clone $query)->count();
$pager = new Pager($total, 25, $page);
$rows = $query
->skip($pager->offset())
->take($pager->perPage())
->get();
Keep the count query and the page query using the same filters. A common bug is counting all rows while displaying filtered rows, which produces incorrect page counts.
Admin tables and search results. Treat the current page as request input, but always cast and clamp it:
$page = max(1, (int) $request->integer('page', 1));
When rendering links, preserve active filters such as q, status, or sort. If the package’s link builder accepts extra query parameters, pass them through. Otherwise, build a small Blade partial that merges request()->except('page') into each link.
API responses. Use the pager to calculate metadata, then return a consistent envelope:
return response()->json([
'data' => $rows,
'meta' => [
'page' => $pager->page(),
'per_page' => $pager->perPage(),
'total' => $total,
'last_page'=> $pager->lastPage(),
],
]);
Confirm the exact method names in the package source; if some are missing, derive them from total and perPage in your own response layer.
Custom markup. If the package ships default link markup, publish or override the view rather than rebuilding links from scratch. For design-system components, wrap the pager output in a Blade component and keep the package responsible only for page/offset logic. This avoids coupling your templates to the package’s internal link format.
Multiple pagers on one page. On dashboards with several tables, namespace the page parameters:
$usersPage = max(1, (int) $request->query('users_page', 1));
$ordersPage = max(1, (int) $request->query('orders_page', 1));
Create one pager per table and pass its own parameter name to the link renderer. Without this, both tables respond to the same page query parameter.
Reusable integration. Put pager creation in a small service, controller trait, or base controller method:
protected function pager(int $total, int $perPage = 25, string $param = 'page'): Pager
{
$page = max(1, (int) request()->query($param, 1));
return new Pager($total, $perPage, $page);
}
This centralizes clamping, request handling, and per-page defaults, and makes the pager easy to fake in tests.
Testing. Unit-test the edge cases directly: page 1, page equal to last page, page beyond last page, zero total, and perPage larger than the result set. Assert that offset() and lastPage() match expectations. For controller tests, assert that changing page changes the returned rows and that filters survive pagination links.
Page numbers are usually 1-based; offsets are 0-based. The most common integration error is passing an offset where the pager expects a page. Clamp with max(1, ...) and verify the package’s expected input in its tests or README.
The package calculates; it does not query. Unlike Laravel’s paginate(), a lightweight pager generally will not run count queries or apply limits for you. You must count, apply skip/take, and fetch the page yourself. This is a feature for control, but it means correctness is your responsibility.
Invalid and out-of-range pages. A negative page, a non-numeric page, or a page beyond the last page can produce empty results or confusing links. Decide on a policy: clamp to the last page, redirect to page 1, or return an empty page with a clear empty state. Document it so different screens behave consistently.
Preserve query strings in links. Pagination links that drop filters are a common UX bug. Merge existing query parameters into link generation and remove only the page parameter when a user changes filters or sorting.
Deep pagination is still expensive. Large offsets on big tables can be slow even with correct math. For very large datasets, prefer cursor pagination or keyset pagination for the underlying query, and use this package only where offset-based paging is acceptable.
Do not mix pagination systems in one view. If you already use Laravel’s LengthAwarePaginator elsewhere, avoid rendering this package’s links next to Laravel paginator links for the same dataset. Choose one system per list to keep page-state behavior predictable.
Check Laravel version constraints. The last release is from 2023. Confirm the package’s composer.json supports your Laravel and PHP versions before adopting it. For a small package the risk is usually low, but version constraints still matter in upgraded applications.
Configuration and views may need publishing. If the package provides config or view publishing, publish them explicitly and commit the customized files. In production, run php artisan config:cache and view:cache after publishing so changes are picked up.
Watch escaping in custom markup. When overriding link views, escape labels and query parameters correctly. Never interpolate raw request input into URLs or HTML without escaping.
Debugging checklist. When paging looks wrong, dump the pager’s page, per-page, offset, and last-page values, then log the SQL limit/offset actually applied. Most issues trace back to a stale total count, a missing filter in the count query, or a page parameter collision between multiple lists.
Extension points. Keep custom behavior in wrappers rather than editing vendor files: a service that builds pagers, a Blade component that renders links, or a subclass if the package exposes overridable methods. That keeps upgrades safe and makes the pagination behavior testable in isolation.
How can I help you explore Laravel packages today?