Skip to content

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=10

Maps to LIMIT 10.

$skip

Skips a number of entities (offset):

GET /odata/Products?$skip=20

Maps to OFFSET 20.

Combined

GET /odata/Products?$top=10&$skip=20&$orderby=id asc

Returns 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

  1. The client optionally sends a Prefer header: Prefer: odata.maxpagesize=50
  2. The server compares the client preference with its configured limits
  3. The effective page size is the minimum of:
    • Client preference (from Prefer header)
    • Server default (config('odata.pagination.default') -- default: 200)
    • Server max (config('odata.pagination.max') -- default: unlimited)

When there are more results than the page size, the response includes a @odata.nextLink:

json
{
  "@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:

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=true

Response:

json
{
  "@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.

OData: MIT | Core: BSL 1.1 | SDK: Commercial License