Pagination
The library supports both client-driven and server-driven pagination.
Client-driven pagination
$top
Limits the number of entities returned:
GET /odata/Products?$top=10Maps to LIMIT 10.
$skip
Skips a number of entities (offset):
GET /odata/Products?$skip=20Maps to OFFSET 20.
Combined
GET /odata/Products?$top=10&$skip=20&$orderby=id ascReturns entities 21-30, sorted by id.
Note: on SQLite, when $skip is used without $top, the resolver adds LIMIT 9223372036854775807 (PHP_INT_MAX) because SQLite requires a LIMIT when OFFSET is present.
Server-driven pagination
When no explicit $top is given, the server applies a default page size from configuration. This prevents unbounded result sets.
How it works
- The client optionally sends a
Preferheader:Prefer: odata.maxpagesize=50 - The server compares the client preference with its configured limits
- The effective page size is the minimum of:
- Client preference (from
Preferheader) - Server default (
config('odata.pagination.default')-- default: 200) - Server max (
config('odata.pagination.max')-- default: unlimited)
- Client preference (from
@odata.nextLink
When there are more results than the page size, the response includes a @odata.nextLink:
{
"@odata.context": "...",
"value": [ ... ],
"@odata.nextLink": "http://localhost/odata/Products?$skip=200"
}The client follows the @odata.nextLink to fetch the next page.
Configuration
In config/odata.php:
'pagination' => [
'max' => 1000, // Maximum page size (clamps client preference)
'default' => 200, // Default when no client preference is sent
],Set max to null for no upper bound.
$count
Request the total count alongside paginated results:
GET /odata/Products?$top=10&$count=trueResponse:
{
"@odata.context": "...",
"@odata.count": 42,
"value": [ ... ]
}The count reflects the total number of matching entities (after $filter and $search), ignoring $top and $skip. See $count for more details.