spatie/laravel-json-api-paginate
A Laravel package that adds a jsonPaginate method to the Eloquent query builder, so pagination reads page[number] and page[size] request parameters and returns the pagination links required by the JSON:API spec.
page[number]/page[size] query semantics and spec-compliant links are a contract obligation, not a nice-to-have — this package delivers that in one line (Model::jsonPaginate()) instead of hand-rolling a custom paginator, presenter, and link builder.BelongsToMany, HasManyThrough), link generation, cursor mode — is a few days of engineering plus permanent test surface. Adopting an MIT-licensed, Spatie-maintained package with Laravel 11/12-era release cadence collapses that to hours. This is the "small, boring, high-leverage dependency" category where buying is almost always correct.links.next URLs — retrofitting pagination semantics later is a breaking change for every client.use_simple_pagination (fewer count queries, but no total — only viable if the UI shows next/prev only), (2) use_cursor_pagination (best for high-volume, append-only feeds; overrides simple), and (3) use_fast_pagination with spatie/laravel-fast-paginate (deferred-join optimization for deep offset paging). A PM can sequence these: ship offset pagination, enable cursor for the noisiest endpoint when p95 page-depletion latency shows up, and add fast paginate only when deep-page DB time is measured as a bottleneck.max_results / default_size are product policy levers — they bound worst-case response size and DB load, and they let you decide whether clients may request page[size]=500 (payload/mobile-data cost, abuse surface) or must stay at 30. number_parameter, size_parameter, cursor_parameter, pagination_parameter, and method_name being configurable means brand-specific or legacy query conventions can be honored without forking.base_url because it overwrote request paths — generating identical pagination URLs for /api/users and /api/posts. For a PM this is a governance cue: the standard is URL::forceScheme() / URL::forceRootUrl() in AppServiceProvider for proxies and TLS termination, not per-package URL overriding. Include this in the API-platform checklist so it isn't rediscovered in production.links.next without client-side URL construction; third-party/integration APIs where spec compliance reduces support tickets and partner onboarding friction; admin or export endpoints needing a hard max_results cap; and any list endpoint where DISTINCT or expensive joins break Laravel's automatic count — the optional third $total argument lets you pass a precomputed count (basic pagination only).Adopt when most of the following hold:
page[number]/page[size] — or are prepared to configure parameter names to match an existing contract.max_results, default_size, mode flags) reviewed once rather than reimplemented per controller.Look elsewhere when:
?page=N&per_page=M API is served perfectly by Laravel's built-in paginators; adopting this adds a dependency and a naming convention you don't need.include, sorting, filtering, and JSON:API error documents are out of scope — evaluate a full server implementation (e.g., laravel-json-api) instead, and treat this package as redundant in that stack.composer why/Packagist stats before citing it as ecosystem validation. Low verified adoption may mean more of the integration risk lands on you.To executives — one paragraph: We can meet the pagination half of our JSON:API commitment with a maintained, MIT-licensed, single-purpose package instead of building it. It is one line of code per endpoint, it enforces a server-side cap on page size (bounding response payloads and database load), and it includes a documented ladder of performance improvements we can switch on from configuration as traffic grows — no controller rewrites, no client-side breaking changes. The cost is essentially a Composer entry and a config file; the alternative is several engineering days plus a permanent test obligation, and a much higher chance of subtle link-generation bugs that clients only discover in production.
To engineering — the specifics: Add spatie/laravel-json-api-paginate; publish config/json-api-paginate.php and set max_results and default_size deliberately. Replace paginate() with jsonPaginate() on Eloquent queries and relationships (BelongsToMany and HasManyThrough supported). Note the API: jsonPaginate($maxResults, $defaultSize, $total) — the third argument injects a precomputed count to dodge DISTINCT/join count problems, and it applies only to basic pagination. For infrastructure-driven URLs, use URL::forceScheme('https') / URL::forceRootUrl() in AppServiceProvider — do not reintroduce the removed base_url behavior; v4 dropped it precisely because it destroyed per-endpoint paths. Keep the three mode flags mutually understood: use_cursor_pagination overrides use_simple_pagination, and use_fast_pagination requires spatie/laravel-fast-paginate (older aaronfrancis/hammerstone variants are also detected), dispatching to fastPaginate()/simpleFastPaginate() with an unchanged return type. IDE stubs ship in-package; if you rename method_name, generate custom hints from _ide_helper.php.
To the API/product owner — what to write into the contract and the acceptance criteria: Document page[number], page[size], and the links object as the client contract; state the enforced maximum page size; state whether meta.total is available (it is not under simple or cursor pagination). Call out explicitly that this covers pagination only — sorting, filtering, sparse fieldsets, and include are separate decisions. Add one non-functional acceptance criterion: a configured max_results is never exceeded regardless of client input, verified by a test.
Risks to name up front: the 0-dependents figure and a maturity score of ~29 / opportunity ~38 suggest this should be adopted as a small utility with a fallback plan rather than a load-bearing platform choice; pin the version, and confirm the v4 base_url removal against any inherited config before rollout.
How can I help you explore Laravel packages today?