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

# Pagination

> Loop through large result sets using stateless offset pagination.

DynamoSQL uses stateless offset pagination. There is no server-side cursor — each page is a fresh query execution that skips past previously-returned rows.

## How It Works

* `options.maxRows` — maximum rows to return per request. Defaults to `100`.
* `options.resumeIdx` — the row offset to start from. Omit (or pass `0`) for the first page.

When the response contains exactly `maxRows` rows, the engine sets `resumeIdx` in the response to `firstRowIdx + maxRows`. Pass that value as `options.resumeIdx` on the next request to get the next page.

When `resumeIdx` is **absent** from the response, the result set is exhausted.

<Warning>
  Check for `resumeIdx` with `"resumeIdx" in data` (presence check), not `data.resumeIdx !== null` or `data.resumeIdx !== undefined`. The field is omitted entirely when there are no more rows — it is never set to `null`.
</Warning>

## Statefulness Caveat

<Warning>
  This is **offset pagination** — the engine re-executes the full query and skips rows on each page. If DynamoDB data changes between page requests, rows may be duplicated or skipped at page boundaries. This approach is appropriate for analytics and reporting workloads, but is not suitable for transactional reads where consistency across pages is required.
</Warning>

## Pagination Loop Examples

<CodeGroup>
  ```python Python theme={null}
  import requests

  def paginate(token, sql, max_rows=100):
      resume_idx = 0
      while True:
          resp = requests.post(
              "https://api.dynamosql.com/v1/query",
              headers={"Authorization": f"Bearer {token}"},
              json={
                  "sql": sql,
                  "mode": "execute",
                  "options": {"maxRows": max_rows, "resumeIdx": resume_idx},
              },
          )
          body = resp.json()
          if not body["success"]:
              raise RuntimeError(body["error"])
          data = body["data"]
          yield from data["data"]
          if "resumeIdx" not in data:
              break
          resume_idx = data["resumeIdx"]

  # Usage
  for row in paginate(token, "SELECT * FROM myschema.orders WHERE status = 'shipped'"):
      print(row)
  ```

  ```javascript Node.js theme={null}
  async function* paginate(token, sql, maxRows = 100) {
    let resumeIdx = 0;

    while (true) {
      const resp = await fetch("https://api.dynamosql.com/v1/query", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          sql,
          mode: "execute",
          options: { maxRows, resumeIdx },
        }),
      });

      const body = await resp.json();
      if (!body.success) {
        throw new Error(body.error);
      }

      const { data } = body;
      yield* data.data;

      if (!("resumeIdx" in data)) {
        break;
      }
      resumeIdx = data.resumeIdx;
    }
  }

  // Usage
  for await (const row of paginate(token, "SELECT * FROM myschema.orders WHERE status = 'shipped'")) {
    console.log(row);
  }
  ```
</CodeGroup>

<Snippet file="pagination-pattern.mdx" />
