> ## Documentation Index
> Fetch the complete documentation index at: https://developers.fibery.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Learn how to paginate through large entity result sets.

## Overview

`q/limit` caps how many Entities one query returns. To read a dataset larger than your page size, paginate with a cursor on `fibery/id`.

## Pagination pattern

1. Order results with `q/order-by: [[["fibery/id"], "q/asc"]]`.
2. Set `q/limit` to your page size + 1. The extra Entity is a sentinel — its presence in the response means another page exists.
3. On every page after the first, filter with `q/where: [">", ["fibery/id"], "$last-seen-id"]` and pass the previous page's last `fibery/id` in `params`.
4. Stop when a response contains `pageSize` Entities or fewer.

<Note>
  If your query already has a `q/where`, combine it with the cursor filter using `["q/and", <existing>, [">", ["fibery/id"], "$last-seen-id"]]`.
</Note>

## Example: page through a database

The first page has no cursor — order by `fibery/id` and request one more than the page size:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      command: 'fibery.entity/query',
      args: {
        query: {
          'q/from': 'Cricket/Player',
          'q/select': ['fibery/id', 'Cricket/Name'],
          'q/order-by': [[['fibery/id'], 'q/asc']],
          'q/limit': 1001
        }
      }
    })
  });
  const data = await response.json();
  ```

  ```bash cURL theme={null}
  curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \
    -H 'Authorization: Token YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '
     {
       "command": "fibery.entity/query",
       "args": {
         "query": {
           "q/from": "Cricket/Player",
           "q/select": ["fibery/id", "Cricket/Name"],
           "q/order-by": [[["fibery/id"], "q/asc"]],
           "q/limit": 1001
         }
       }
     }
    '
  ```
</CodeGroup>

If the response contains more than 1000 Entities, drop the last one, take the `fibery/id` of the 1000th, and request the next page using it as a cursor:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      command: 'fibery.entity/query',
      args: {
        query: {
          'q/from': 'Cricket/Player',
          'q/select': ['fibery/id', 'Cricket/Name'],
          'q/where': ['>', ['fibery/id'], '$last-seen-id'],
          'q/order-by': [[['fibery/id'], 'q/asc']],
          'q/limit': 1001
        },
        params: { '$last-seen-id': '21e578b0-9752-11e9-81b9-4363f716f666' }
      }
    })
  });
  const data = await response.json();
  ```

  ```bash cURL theme={null}
  curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \
    -H 'Authorization: Token YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '
     {
       "command": "fibery.entity/query",
       "args": {
         "query": {
           "q/from": "Cricket/Player",
           "q/select": ["fibery/id", "Cricket/Name"],
           "q/where": [">", ["fibery/id"], "$last-seen-id"],
           "q/order-by": [[["fibery/id"], "q/asc"]],
           "q/limit": 1001
         },
         "params": {
           "$last-seen-id": "21e578b0-9752-11e9-81b9-4363f716f666"
         }
       }
     }
    '
  ```
</CodeGroup>

A reusable helper that wraps the loop:

```javascript JavaScript theme={null}
const PAGE_SIZE = 1000;
const QUERY_LIMIT = PAGE_SIZE + 1;

async function queryEntitiesPaginated({ query, params }) {
  const allEntities = [];
  let lastSeenId = null;
  let hasMorePages = true;

  while (hasMorePages) {
    const paginatedQuery = {
      ...query,
      'q/order-by': [[['fibery/id'], 'q/asc']],
      'q/limit': QUERY_LIMIT,
    };

    if (lastSeenId) {
      const existingWhere = query['q/where'];
      paginatedQuery['q/where'] = existingWhere
        ? ['q/and', existingWhere, ['>', ['fibery/id'], '$last-seen-id']]
        : ['>', ['fibery/id'], '$last-seen-id'];
    }

    const paginatedParams = lastSeenId
      ? { ...params, '$last-seen-id': lastSeenId }
      : params;

    const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
      method: 'POST',
      headers: {
        'Authorization': 'Token YOUR_TOKEN',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        command: 'fibery.entity/query',
        args: { query: paginatedQuery, params: paginatedParams },
      }),
    });
    const data = await response.json();
    const entities = data.result;

    if (entities.length > PAGE_SIZE) {
      const pageEntities = entities.slice(0, PAGE_SIZE);
      allEntities.push(...pageEntities);
      lastSeenId = pageEntities[pageEntities.length - 1]['fibery/id'];
    } else {
      allEntities.push(...entities);
      hasMorePages = false;
    }
  }

  return allEntities;
}
```

## Loading collections and nested graphs

Collection sub-queries can't be paginated directly. In every sub-query, set `q/limit: 100` and treat any collection that comes back at exactly the limit as potentially truncated.

To get the full collection for those parents, query the child Database directly with the cursor pattern, scoped to one parent at a time.

Step 1 — page through parents, selecting each parent's collection with `q/limit: 100`:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      command: 'fibery.entity/query',
      args: {
        query: {
          'q/from': 'Cricket/Team',
          'q/select': [
            'fibery/id',
            'Cricket/Name',
            { 'Cricket/Current Players': {
                'q/select': ['fibery/id', 'Cricket/Name'],
                'q/limit': 100
              }
            }
          ],
          'q/order-by': [[['fibery/id'], 'q/asc']],
          'q/limit': 1001
        }
      }
    })
  });
  const data = await response.json();
  ```

  ```bash cURL theme={null}
  curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \
    -H 'Authorization: Token YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '
     {
       "command": "fibery.entity/query",
       "args": {
         "query": {
           "q/from": "Cricket/Team",
           "q/select": [
             "fibery/id",
             "Cricket/Name",
             { "Cricket/Current Players": {
                 "q/select": ["fibery/id", "Cricket/Name"],
                 "q/limit": 100
               }
             }
           ],
           "q/order-by": [[["fibery/id"], "q/asc"]],
           "q/limit": 1001
         }
       }
     }
    '
  ```
</CodeGroup>

Step 2 — for any parent whose collection came back at 100 items, paginate the child Database directly. Filter by the back-reference Field that points from the child to the parent (here, `Cricket/Current Team` on `Cricket/Player`), combined with the `fibery/id` cursor:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      command: 'fibery.entity/query',
      args: {
        query: {
          'q/from': 'Cricket/Player',
          'q/select': ['fibery/id', 'Cricket/Name'],
          'q/where': [
            'q/and',
            ['=', ['Cricket/Current Team', 'fibery/id'], '$team-id'],
            ['>', ['fibery/id'], '$last-seen-id']
          ],
          'q/order-by': [[['fibery/id'], 'q/asc']],
          'q/limit': 1001
        },
        params: {
          '$team-id': '21e578b0-9752-11e9-81b9-4363f716f666',
          '$last-seen-id': '216c2a00-9752-11e9-81b9-4363f716f666'
        }
      }
    })
  });
  const data = await response.json();
  ```

  ```bash cURL theme={null}
  curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \
    -H 'Authorization: Token YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '
     {
       "command": "fibery.entity/query",
       "args": {
         "query": {
           "q/from": "Cricket/Player",
           "q/select": ["fibery/id", "Cricket/Name"],
           "q/where": [
             "q/and",
             ["=", ["Cricket/Current Team", "fibery/id"], "$team-id"],
             [">", ["fibery/id"], "$last-seen-id"]
           ],
           "q/order-by": [[["fibery/id"], "q/asc"]],
           "q/limit": 1001
         },
         "params": {
           "$team-id": "21e578b0-9752-11e9-81b9-4363f716f666",
           "$last-seen-id": "216c2a00-9752-11e9-81b9-4363f716f666"
         }
       }
     }
    '
  ```
</CodeGroup>

The same approach extends to deeper graphs: page through the top-level Entities, then for each one page through its children, then for each child page through its grandchildren, and so on.

<Callout icon="circle-info" color="#199EE3">
  This pattern assumes the child Database has a single Field pointing back to the parent (a one-to-many relation). For many-to-many collections, filter the child Database by the inverse collection Field with `q/in`, passing parent ids as a list — for example `"q/where": ["q/in", ["Cricket/Former Players", "fibery/id"], "$player-ids"]`. Combine with the `fibery/id` cursor under `q/and` as in Step 2.
</Callout>

## Avoid "q/no-limit"

<Callout icon="circle-exclamation" color="#fba32f">
  `"q/no-limit"` returns every matching Entity in a single response. On large Databases the request will time out. Use bounded limits and the pagination pattern above.
</Callout>

**Top-level queries.** `"q/no-limit"` is supported but discouraged. Use `q/limit: 1000` and the cursor pattern.

**Collection sub-queries.** `"q/no-limit"` is allowed today but planned for deprecation. Use `q/limit: 100` in sub-queries; if you need the full collection, paginate the child Database separately as shown in [Loading collections and nested graphs](#loading-collections-and-nested-graphs).
