{"id":15188309,"url":"https://github.com/juampi92/cursor-pagination","last_synced_at":"2025-10-02T03:30:36.127Z","repository":{"id":46284192,"uuid":"122583097","full_name":"juampi92/cursor-pagination","owner":"juampi92","description":"Cursor pagination for your Laravel API","archived":true,"fork":false,"pushed_at":"2022-09-08T18:46:38.000Z","size":49,"stargazers_count":71,"open_issues_count":4,"forks_count":16,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-30T12:51:46.889Z","etag":null,"topics":["api","laravel","laravel5","pagination","php"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juampi92.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-23T06:38:26.000Z","updated_at":"2025-01-23T05:18:31.000Z","dependencies_parsed_at":"2022-09-16T03:40:27.841Z","dependency_job_id":null,"html_url":"https://github.com/juampi92/cursor-pagination","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/juampi92/cursor-pagination","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Fcursor-pagination","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Fcursor-pagination/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Fcursor-pagination/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Fcursor-pagination/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juampi92","download_url":"https://codeload.github.com/juampi92/cursor-pagination/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Fcursor-pagination/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277950332,"owners_count":25904480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-02T02:00:08.890Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","laravel","laravel5","pagination","php"],"created_at":"2024-09-27T19:02:51.701Z","updated_at":"2025-10-02T03:30:35.861Z","avatar_url":"https://github.com/juampi92.png","language":"PHP","funding_links":[],"categories":["Packages"],"sub_categories":["Views"],"readme":"\n# Cursor Pagination for Laravel\n[![Latest Version](https://img.shields.io/github/release/juampi92/cursor-pagination.svg?style=flat-square)](https://github.com/juampi92/cursor-pagination/releases)\n[![Build Status](https://img.shields.io/travis/juampi92/cursor-pagination/master.svg?style=flat-square)](https://travis-ci.org/juampi92/cursor-pagination)\n[![StyleCI](https://styleci.io/repos/122583097/shield?branch=master)](https://styleci.io/repos/122583097)\n[![Total Downloads](https://img.shields.io/packagist/dt/juampi92/cursor-pagination.svg?style=flat-square)](https://packagist.org/packages/juampi92/cursor-pagination)\n[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)\n\n**⚠️ Cursor pagination is archived.** This is because [cursor pagination](https://laravel.com/docs/pagination#cursor-pagination) is built into Laravel out of the box so is no longer needed. We recommend that you use the native package, or if you must, fork this one.\n\n----\n\nThis package provides a cursor based pagination already integrated with Laravel's [query builder](https://laravel.com/docs/master/queries) and [Eloquent ORM](https://laravel.com/docs/master/eloquent).\nIt calculates the SQL query limits automatically by checking the requests GET parameters, and automatically builds\nthe next and previous urls for you.\n\n## Installation\n\nYou can install this package via composer using:\n\n```bash\ncomposer require juampi92/cursor-pagination\n```\n\nThe package will automatically register itself.\n\n### Config\n\nTo publish the config file to `config/cursor_pagination.php` run:\n\n````bash\nphp artisan vendor:publish --provider=\"Juampi92\\CursorPagination\\CursorPaginationServiceProvider\" --tag=\"config\"\n````\n\nThis will publish the following [file](config/cursor_pagination.php). \nYou can customize the name of the GET parameters, as well as the default items per page. \n\n## How does it work\n\nThe main idea behind a cursor pagination is that it needs a context to know what results to show next.\nSo instead of saying `page=2`, you say `next_cursor=10`. The result is the same as the old fashioned page pagination,\nbut now you have more control on the output, cause `next_cursor=10` should always return the same (unless some records are deleted).\n \nThis kind of pagination is useful when you sort using the latest first, so you can keep an infinite scroll using `next_cursor` whenever you get to the bottom,\nor do a `previous_cursor` whenever you need to refresh the list at the top, and if you send the first cursor,\nthe results will only fetch the new ones.\n\n#### Pros\n - New rows don't affect the result, so no duplicated results when paginating.\n - Filtering by an indexed cursor is way faster that using database offset.\n - Using previous cursor to avoid duplicating the first elements.\n \n#### Cons\n - No previous page, although the browser still has them.\n - No navigating to arbitrary pages (you must know the previous result to know the next ones).\n\nBasically, **it's perfect for infinite scrolling!**\n\n## Basic Usage\n\n### Paginating Query Builder Results\n    \nThere are several ways to paginate items. \nThe simplest is by using the `cursorPaginate` method on the **query builder** or an **Eloquent query**. \nThe `cursorPaginate` method automatically takes care of setting the proper limit and fetching the next or \nprevious elements based on the cursor being viewed by the user.\nBy default, the `cursor` is detected by the value of the page query string argument on the HTTP request.\nThis value is automatically detected by the package taking your custom config into account, and is also automatically inserted into links and meta generated by the paginator.\n\n````php\npublic function index()\n{\n    $users = DB::table('users')-\u003ecursorPaginate();\n    return $users;\n}\n````\n\n### Paginating Eloquent Results\n\nYou may also paginate **Eloquent queries**. In this example, we will paginate the `User` model \nwith `15` items per page. As you can see, the syntax is identical to paginating query builder results:\n\n````php\n$users = User::cursorPaginate(15);\n````\n\nOf course, you may call paginate after setting other constraints on the query, such as where clauses:\n\n````php\n$users = User::where('votes', '\u003e', 100)-\u003ecursorPaginate(15);\n````\n\nOr sorting your results:\n\n````php\n$users = User::orderBy('id', 'desc')-\u003ecursorPaginate(15);\n````\n\nDon't worry, the package will detect if the model's primary key is being used for sorting, and it will adapt\nitself so the next and previous work as expected.\n\n### Identifier\n\nThe paginator identifier is basically the cursor attribute. The model's attribute that will be used for paginating.\nIt's really important that this identifier is **unique** on the results. Duplicated identifiers could cause that some\nrecords are not showed, so be careful.\n\n*If the query is not sorted by the identifier, the cursor pagination might not work as expected.*\n\n####  Auto-detecting Identifier\n\nIf no identifier is defined, the cursor will try to figure it out by itself. First, it will check if there's any orderBy clause.\nIf there is, it will take the **first column** that is sorted and will use that.\nIf there is not any orderBy clause, it will check if it's an Eloquent model, and will use it's `primaryKey` (by default it's 'id').\nAnd if it's not an Eloquent Model, it'll use `id`.\n\n##### Example\n\n````php\n// Will use Booking's primaryKey\nBookings::cursorPaginate(10);\n````\n\n````php\n// Will use hardcoded 'id'\nDB::table('bookings')-\u003ecursorPaginate(10);\n````\n\n````php\n// Will use 'created_by'\nBookings::orderBy('created_by', 'asc')\n    -\u003ecursorPaginate(10);\n````\n\n#### Customizing Identifier\n\nJust define the `identifier` option\n\n````php\n// Will use _id, ignoring everything else.\nBookings::cursorPaginate(10, ['*'], [\n    'identifier'      =\u003e '_id'\n]);\n````\n\n### Date cursors\n\nBy default, the identifier is the model's primaryKey (or `id` in case it's not an Eloquent model) so there are no duplicates,\nbut you can adjust this by passing a custom `identifier` option. In case that specified identifier is casted to date or datetime\non Eloquent's `$casts` property, the paginator will convert it into **unix timestamp** for you.\n\nYou can also specify it manually by adding a `[ 'date_identifier' =\u003e true ]` option.\n\n##### Example\n\nUsing Eloquent (make sure Booking Model has ` protected $casts = ['datetime' =\u003e 'datetime']; `)\n\n````php\n// It will autodetect 'datetime' as identifier,\n//   and will detect it's casted to datetime.\nBookings::orderBy('datetime', 'asc')\n    -\u003ecursorPaginate(10);\n````\n\nUsing Query\n````php\n// It will autodetect 'datetime' as identifier,\n//   but since there is no model, you'll have to\n//   specify the 'date_identifier' option to `true`\nDB::table('bookings')\n    -\u003eorderBy('datetime', 'asc')\n    -\u003ecursorPaginate(10, ['*'], [\n        'date_identifier' =\u003e true\n    ]);\n````\n\n### Inherits Laravel's Pagination\n\nYou should know that CursorPaginator inherits from Laravel's AbstractPaginator, so methods like\n`withPath`, `appends`, `fragment` are available, so you can use those to build the url,\nbut you should know that the default url creation of this package includes query params that you\nmight already have.\n\n## Displaying Pagination Results\n\n### Converting to JSON\n\nA basic return will transform the paginator to JSON and will have a result like this:\n\n````php\nRoute::get('api/v1', function () {\n    return App\\User::cursorPaginate();\n});\n````\n\nCalling `api/v1` will output:\n\n````json\n{\n   \"path\": \"api/v1?\",\n   \"previous_cursor\": \"10\",\n   \"next_cursor\": \"3\",\n   \"per_page\": 3,\n   \"next_page_url\": \"api/v1?next_cursor=3\",\n   \"prev_page_url\": \"api/v1?previous_cursor=1\",\n   \"data\": [\n        {}\n   ]\n}\n````\n\n### Using a Resource Collection\n\nBy default, Laravel's API Resources when using them as collections, they will output a paginator's metadata\ninto `links` and `meta`.\n\n````json\n{\n   \"data\":[\n        {}\n   ],\n   \"links\": {\n       \"first\": null,\n       \"last\": null,\n       \"prev\": \"api/v1?previous_cursor=1\",\n       \"next\": \"api/v1?next_cursor=3\"\n   },\n   \"meta\": {\n       \"path\": \"api/v1?\",\n       \"previous_cursor\": \"1\",\n       \"next_cursor\": \"3\",\n       \"per_page\": 3\n   }\n}\n````\n \n## Understanding the previous cursor\n\nIt's important to clarify that the previous cursor does NOT work like the next one.\nThe next cursor makes the paginator return the elements that follow that cursor.\n\nSo in the case of the next cursor being `10`, that pagination should return `next + 1`, `next + 2`, `next + 3`.\nSo that works as expected.\n\nThe previous cursor though it's not `prev - 1`, `prev - 2`, `prev - 3`. No. It does not return the adjacent elements, or the 'context' for that cursor.\nWhat it does instead is pretty interesting:\n\nLet's assume we do a simple fetch and it returns elements `[10, 9, 8]`. If we do a `prev=10`, it will return empty, cause\nthose are the first elements. So if we now add 5 more to that: from the 15 to the 11, and we do `prev=10` again, the\nresult will be `[15, 14, 13]`. **NOT** `[13, 12 ,11]`.\n\nThat's because previous works like a filter. It shows the first 'per_page' items that are before that cursor.\nThe order is the same as in the next cursor, so it fetches the latest ones.\n\nThe same logic can be used when combining cursors: `next=13\u0026prev=10` will output the missing two elements: `[12, 11]`, so you can complete the list without fetching any extra items.\n\n## Customizing per page results\n\nWith this plugin you can specify the perPage number by many ways.\n\nYou can set the `cursor_pagination.per_page` config, so if you don't send any parameters, it will use that as the default.\n\nYou can also use an array. The purpose of the array declaration is to separate whenever it's a previous cursor, or a next / default cursor.\nThis way, when you do a *'refresh'*, you can fetch more results than if you are simply scrolling.\n \nTo configure that, set `$perPage = [15, 5]`. That way it'll fetch 15 when you do a `previous_cursor`, and 5 when you do a normal fetch or a `next_cursor`. \n\n## Customizing param names\n\nOn the config, change `identifier_name` to change the word `cursor`. Examples: `cursor`, `id`, `pointer`, etc. \n  \nChange `navigation_names` to change the words `['previous', 'next']`.\nExamples:  `['before', 'after']` , `['min', 'max']`\n\n##### Transforming the result:\nEdit `transform_name` to format the string differently:\n\n- For `previousCursor` use 'camel_case'\n- For `previous-cursor` use 'kebab_case'.\n- For `previous_cursor` use 'snake_case'.\n\nUsing `null` defaults to camelCase.\n\n## API Docs\n\n### Paginator custom options\n\n````php\nnew CursorPaginator(array|collection $items, array|int $perPage, array options = [\n    // Attribute used for choosing the cursor. Used primaryKey on Eloquent Models as default.\n    'identifier'       =\u003e 'id',\n    'identifier_alias' =\u003e 'id',\n    'date_identifier'  =\u003e false,\n    'path'             =\u003e request()-\u003epath(),\n]);\n````\n\n(*The items must have a $item-\u003e{$identifier} property.*)\n\nEloquent Builder and Query Builder's macro:\n\n````php\ncursorPaginate(array|int $perPage, array $cols = ['*'], array options = []): CursorPaginator;\n````\n\n### Paginator methods\n\n ````php\n $resutls-\u003ehasMorePages(): bool;\n $results-\u003enextCursor(): string|null;\n $results-\u003eprevCursor(): string|null;\n $results-\u003epreviousPageUrl(): string|null;\n $results-\u003enextPageUrl(): string|null;\n $results-\u003eurl(['next' =\u003e 1]): string;\n $results-\u003ecount(): int;\n ````\n  \n Note: all cursors are casted as strings.\n\n### Identifier Alias\n\nSometimes we need to use the paginator on a JOIN query. This might have duplicated columns, so we have to specify one for the sorting.\nSince Mysql doesn't allow us to use a selector alias on a WHERE condition, we have to use the `table`.`column` name instead.\n\n```php\n$following = $user-\u003efollowing()\n    -\u003eorderBy('follows.created_at', 'desc')\n    -\u003ecursorPaginate(10, ['*'], [\n        'date_identifier' =\u003e true,\n        'identifier' =\u003e 'follows.created_at',\n        'identifier_alias' =\u003e 'created_at',\n    ]);\n```\n\nBy default, the package will guess that the `identifier_alias` is the column name you specify,\nso if you order by `follows.created_at`, it will try to use the models `created_at` as identifier (by using the alias).\n\n\n\n## Testing\n\nRun the tests with:\n```bash\nvendor/bin/phpunit\n```\n\n## Credits\n\n- [Juan Pablo Barreto](https://github.com/juampi92)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuampi92%2Fcursor-pagination","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuampi92%2Fcursor-pagination","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuampi92%2Fcursor-pagination/lists"}