Open-source project
spatie/laravel-json-api-paginate avatar
spatie/laravel-json-api-paginate

laravel-json-api-paginate: page[number] and page[size] for Laravel Eloquent

A paginator that plays nice with the JSON API spec

644 stars47 forksPHPMIT

At a glance

What is it?
Spatie's package adds a jsonPaginate macro to the Eloquent query builder so a Laravel API listens to the page[number] and page[size] parameters the JSON API spec expects, and returns the pagination links the spec requires. It also supports cursor pagination and a fast paginate mode that avoids the deep offset problem.
Who is it for?
laravel-json-api-paginate suits a Laravel team whose public API has to follow the JSON API specification, because the parameter names and the response links are the parts clients actually notice and the spec is not optional in that case. It does not suit you if your API is a private contract with your own clients, because a bare Laravel paginator already does the job.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The mismatch is parameter names, not missing features

The problem this package solves is narrow, and understanding why keeps the rest of the design in proportion.

In a vanilla Laravel application, the query builder paginators listen to the page request parameter. That works fine, but it does not follow the example solution of the json:api spec, which expects the query builder paginator to listen to the page[number] and page[size] request parameters. The spec also has its own rules for how pagination links are returned, under the fetching pagination section of the format document.

So the gap is a compatibility gap. Laravel has pagination; the JSON API specification expects a particular parameter naming and a particular link structure; this package bridges the two with one macro.

The implementation is a single method added to the Eloquent query builder, called jsonPaginate. Calling it is the entire usage surface:

php
YourModel::jsonPaginate();

Because it is a builder macro rather than a replacement paginator, the ordinary query builder methods still work in front of it:

php
YourModel::where('my_field', 'myValue')->jsonPaginate();

And relations can be paginated the same way:

php
$model = YourModel::find(1);

$model->relation()->jsonPaginate();

That last one matters more than it looks. Paginating a relation is exactly where the JSON API specification is most demanding, because a relationship is a resource in its own right and the links have to describe the relationship rather than the collection, so having the paginator work through the builder means the same method covers both cases.

Everything the package does is behind one config file

The package has no magic and no hidden state, which is why the config file is the best thing to read before adopting it.

Publishing it is a single artisan command:

bash
php artisan vendor:publish --provider="Spatie\JsonApiPaginate\JsonApiPaginateServiceProvider" --tag="config"

The resulting config/json-api-paginate.php holds the whole configuration surface, and the keys that matter are these:

php
    'max_results' => 30,
    'default_size' => 30,
    'number_parameter' => 'number',
    'size_parameter' => 'size',
    'cursor_parameter' => 'cursor',
    'method_name' => 'jsonPaginate',
    'use_simple_pagination' => false,
    'use_cursor_pagination' => false,
    'use_fast_pagination' => false,
    'pagination_parameter' => 'page',

The three parameter keys are the mechanism. number_parameter, size_parameter and cursor_parameter set the x in page[x], so the request ends up looking like page[number]=2&page[size]=50, which is the shape the specification describes. The pagination_parameter key is the page part itself.

The method_name key is unusual and useful. Because the macro is named through configuration, you can rename it, which matters if jsonPaginate collides with something else in your application, or if you want the API to read consistently with your other macros.

The two pagination mode flags are switches between different query strategies. use_simple_pagination is described in the config as being for the case where you only need Next and Previous links, since simple pagination performs a more efficient query. use_cursor_pagination overrides use_simple_pagination if both are on. use_fast_pagination is a third mode and is discussed separately below.

The service provider registers itself automatically in Laravel 5.5 and above. On older framework versions you add it to the providers array in config/app.php yourself.

Cursor pagination and the flag that overrides everything else

Cursor pagination is the third mode, and the README quotes the Laravel framework's own definition of it: while paginate and simplePaginate create queries using the SQL offset clause, cursor pagination works by constructing where clauses that compare the values of the ordered columns contained in the query, providing the most efficient database performance available amongst all of Laravel's pagination methods.

The practical difference is that a cursor query has no offset, so a deep page does not get slower, and the response carries a cursor rather than a page number. That is a different resource representation from what the classic page number approach gives you, and the spec's pagination section covers both shapes.

To turn it on you set use_cursor_pagination to true in the config, and the cursor_parameter key controls the name of the cursor field in the request. Since this flag overrides use_simple_pagination, the interaction between the two is worth noting before you enable both.

There is one important caveat about the other override, the total count. You can pass the total count to the paginate function directly, which the documentation says is useful for performance reasons or to prevent issues with the DISTINCT keyword, and the call looks like this:

php
$total = 42;

YourModel::jsonPaginate(null, null, $total);

But the documentation marks this as effective only with basic pagination, with no effect with cursor, simple or fast pagination. If you are relying on a supplied total, that only works on the plain paginator, which is an easy thing to miss when you switch modes.

The same pattern applies to the two size arguments. The maximum results can be overridden per call, and the default size can be overridden per call:

php
$maxResults = 60;

YourModel::jsonPaginate($maxResults);
php
$defaultSize = 15;

YourModel::jsonPaginate(null, $defaultSize);

The argument order is maximum first, then default, then total, so passing the default size means passing null for the maximum.

Fast pagination needs a second package, and the older ones still work

The performance option is the third mode and it does not work on its own.

The problem it addresses is a database one. With limit and offset pagination, the database gets slower the deeper you page, because it has to walk past every skipped row. A deferred join avoids that by first fetching only the primary keys for the page, and then fetching the full rows for those keys. So the cost of page fifty is roughly the cost of page one, instead of the cost of skipping forty-nine rows.

To get that behaviour you install a separate package:

bash
composer require spatie/laravel-fast-paginate

and then set use_fast_pagination to true in the config file. From then on jsonPaginate calls fastPaginate instead of paginate, or simpleFastPaginate when use_simple_pagination is on. Nothing else changes, as the method signature and the returned paginator stay the same, which is the point of doing it through a config flag rather than by switching calls in your controllers.

The documentation also notes that the older aaronfrancis/fast-paginate and hammerstone/fast-paginate packages are recognised as well, so a project that already has one of those installed does not have to change anything beyond flipping the flag.

The trade-off is that this adds a dependency whose own version has to stay compatible with your Laravel version, and it changes the SQL that runs against your database. If you rely on a total count for the JSON API meta object, the fast path does not support it, which connects back to the limitation on the total argument.

A 2.x line with a recent release, and an MIT licence

The release history is short and recent, which is what you want from a package that sits in the request path of a public API.

Version 2.0.0 was published on 2026-02-03, 2.0.1 on 2026-02-21 and 2.0.2 on 2026-08-07, and the last push to the repository was on 2026-08-07. The 2.0 line is recent enough that the major bump happened this year, and the fact that the newest release and the newest push share a date suggests the 2.0.2 tag is the current state of the work rather than a snapshot of an older branch.

The repository is a conventional Composer package. The top level holds composer.json, src/ for the implementation, config/ for the config file, tests/ and phpunit.xml.dist for the test suite, a .php-cs-fixer.php with its cache and a .phpunit.result.cache, a .editorconfig, a .gitattributes, a CHANGELOG.md, an _ide_helper.php for IDE support, and a LICENSE.md.

The licence is MIT, with the LICENSE.md file in the repository root, and the package is published on Packagist as spatie/laravel-json-api-paginate.

For support, the README points at the Spatie open source page and asks for contributions, and the usual route for questions in a package this small is the issue tracker. The homepage is the Spatie open source page for Laravel packages.

One thing the package does not do is render the JSON API document itself. It produces pagination that matches the specification, and the rest of the resource, the data, the included section and the top level links are your application's job or another package's. That boundary is worth being clear about before you adopt it, because the spec covers much more than pagination.

Editorial conclusion

laravel-json-api-paginate suits a Laravel team whose public API has to follow the JSON API specification, because the parameter names and the response links are the parts clients actually notice and the spec is not optional in that case. It does not suit you if your API is a private contract with your own clients, because a bare Laravel paginator already does the job. Before you adopt it, read the published config file rather than the defaults, since the maximum page size of 30 and the use_fast_pagination flag change the shape of the query, and note that the fast paginate behaviour needs the separate spatie/laravel-fast-paginate package installed.

Frequently asked questions

How do I install laravel-json-api-paginate?

Run composer require spatie/laravel-json-api-paginate. The service provider registers automatically in Laravel 5.5 and above, and the config file can be published with php artisan vendor:publish using the JsonApiPaginateServiceProvider provider and the config tag.

Which request parameters does jsonPaginate read?

The page[number] and page[size] parameters that the json:api specification expects. The names are configurable through the number_parameter and size_parameter config keys, and the page prefix itself through pagination_parameter.

Does laravel-json-api-paginate support cursor pagination?

Yes. Set use_cursor_pagination to true in the config file. It overrides use_simple_pagination if both are enabled, and the cursor field name comes from the cursor_parameter key.

What does use_fast_pagination do in laravel-json-api-paginate?

It makes jsonPaginate call fastPaginate, or simpleFastPaginate when simple pagination is on, instead of paginate. You need to install spatie/laravel-fast-paginate separately. The method signature and the returned paginator stay the same. The older aaronfrancis/fast-paginate and hammerstone/fast-paginate packages are recognised as well.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. spatie/laravel-json-api-paginate on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/spatie-laravel-json-api-paginate.svg)](https://hysenlabs.com/projects/spatie-laravel-json-api-paginate)

Was this useful?