# Paginate results

Source: https://docs.graphops.xyz/subgraphs/guides/paginate-results

Read every pool, holder, or vote in a collection for a report or a sync job. Order by a stable
field and ask for the rows after the last `id` you received, so each request is bounded and a
worker can resume where it stopped.

## Prerequisites

* An API key that includes Subgraphs.
* A Subgraph endpoint that exposes the entity collection you want to scan.
* A stable ordering field such as `id`, `createdAtTimestamp`, or another indexed field in the
Subgraph schema.

## Choose a pagination method

For a few pages, `first` and `skip` are usually enough. For long scans, filter past the last value
of a stable field, such as `where: { id_gt: $lastId }` with `orderBy: id`. Each page then starts
where the previous one ended, however far into the collection you are:

*Each page asks for the rows after the last id of the page before it. Save that id after you process a page, and stop when a page returns fewer rows than you requested.*

Each page: `pools(first: 100, where: { id_gt: $lastId }, orderBy: id, orderDirection: asc)`

- Page 1: `id_gt 0x00…00`, 100 rows.
- Page 2: `id_gt 0x3a…f1`, 100 rows.
- Page 3: `id_gt 0x7c…02`, 37 rows · stop.

## Request

This request reads one page of up to 10 pools in the current indexed view after `$lastId`. For the first page, set `$lastId` to a
value that sorts before every `id`, such as the zero address in these examples.

CLI · curl

Prerequisites

1. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

2. Replace `<ALIAS>` with your Endpoint Alias.

[Example project](/examples/subgraphs.guides.paginate-results.sh-1.tar.gz)

```sh
# Start below every id. For the next page, set lastId to the last id in this response.
curl --fail-with-body --silent --show-error "https://subgraphs.graphops.xyz/v1/alias/<ALIAS>" \
  -H "Authorization: Bearer ${GRAPHOPS_API_KEY}" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query PoolsPage($first: Int!, $lastId: ID!) { pools(first: $first, where: { id_gt: $lastId }, orderBy: id, orderDirection: asc) { id token0 { symbol } token1 { symbol } } _meta { block { number hash } } }",
    "variables": { "first": 10, "lastId": "0x0000000000000000000000000000000000000000" },
    "operationName": "PoolsPage"
  }'

```

Output

```json
{
  "data": {
    "pools": [
      {
        "id": "0x000024feb293b6c6c3a80a95f1f830a8746400b9",
        "token0": {
          "symbol": "TRUMP"
        },
        "token1": {
          "symbol": "WETH"
        }
      },
      {
        "id": "0x0000d36ab86d213c14d93cd5ae78615a20596505",
        "token0": {
          "symbol": "RALPH"
        },
        "token1": {
          "symbol": "WETH"
        }
      },
      {
        "id": "0x00013bffe4c38c1372a450fac78b01cb4f554781",
        "token0": {
          "symbol": "Pectra"
        },
        "token1": {
          "symbol": "WETH"
        }
      },
      {
        "id": "0x0001fcbba8eb491c3ccfeddc5a5caba1a98c4c28",
        "token0": {
          "symbol": "BCZ"
        },
        "token1": {
          "symbol": "WETH"
        }
      },
      {
        "id": "0x00024b66a38a676c0eae59126c1367ad141afa79",
        "token0": {
          "symbol": "WETH"
        },
        "token1": {
          "symbol": "TI"
        }
      },
      {
        "id": "0x0002e63328169d7feea121f1e32e4f620abf0352",
        "token0": {
          "symbol": "wNXM"
        },
        "token1": {
          "symbol": "ICHI"
        }
      },
      {
        "id": "0x0003c9d75e102b8a9fe77280ca410e9135beb150",
        "token0": {
          "symbol": "TRANCHE-bWBTC-A"
        },
        "token1": {
          "symbol": "USDT"
        }
      },
      {
        "id": "0x0003e7d8d22d0e8e53d56885069fc8a254f0431f",
        "token0": {
          "symbol": "RLP"
        },
        "token1": {
          "symbol": "USDC"
        }
      },
      {
        "id": "0x0004cd8474e882278e32e584699090be496f410e",
        "token0": {
          "symbol": "ZERO"
        },
        "token1": {
          "symbol": "USDC"
        }
      },
      {
        "id": "0x0005079292756443f2e711570f8ab4e1951d4f97",
        "token0": {
          "symbol": "EDG"
        },
        "token1": {
          "symbol": "WETH"
        }
      }
    ],
    "_meta": {
      "block": {
        "number": 26285621,
        "hash": "0x40c3154aaa484a99057c58a6c72c27171ed861d260179db387850027655d0881"
      }
    }
  }
}
```

Up to $first rows, ordered by id

Last id: your next $lastId

Log with each page

TypeScript · fetch

Prerequisites

1. Install Node.js.

2. Install tsx. `npm install tsx`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

4. Replace `<ALIAS>` with your Endpoint Alias.

Tested with Node.js 24.22.1, tsx 4.23.15.

[Example project](/examples/subgraphs.guides.paginate-results.ts-2.tar.gz)

```typescript
export {};

// Start below every id. For the next page, pass the last id this page returns.
const lastId = "0x0000000000000000000000000000000000000000";

const response = await fetch(
  "https://subgraphs.graphops.xyz/v1/alias/<ALIAS>",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GRAPHOPS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: `
        query PoolsPage($first: Int!, $lastId: ID!) {
          pools(
            first: $first
            where: { id_gt: $lastId }
            orderBy: id
            orderDirection: asc
          ) {
            id
            token0 { symbol }
            token1 { symbol }
          }
          _meta { block { number hash } }
        }
      `,
      variables: { first: 10, lastId },
      operationName: "PoolsPage",
    }),
  },
);
if (!response.ok) throw new Error(await response.text());

const result = await response.json();
// The last id is the cursor for the next page; null means there are no more rows.
console.log(JSON.stringify(result.data.pools.at(-1)?.id ?? null));

```

Output

```json
"0x0005079292756443f2e711570f8ab4e1951d4f97"
```

Use this cursor as lastId in the next request

Go · net/http

Prerequisites

1. Install Go.

2. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

3. Replace `<ALIAS>` with your Endpoint Alias.

Tested with Go 1.26.0.

[Example project](/examples/subgraphs.guides.paginate-results.go-5.tar.gz)

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	// Start below every id. For the next page, pass the last id this page returns.
	lastID := "0x0000000000000000000000000000000000000000"

	payload, err := json.Marshal(struct {
		Query         string         `json:"query"`
		Variables     map[string]any `json:"variables"`
		OperationName string         `json:"operationName"`
	}{
		Query: `
        query PoolsPage($first: Int!, $lastId: ID!) {
          pools(
            first: $first
            where: { id_gt: $lastId }
            orderBy: id
            orderDirection: asc
          ) {
            id
            token0 { symbol }
            token1 { symbol }
          }
          _meta { block { number hash } }
        }
        `,
		Variables:     map[string]any{"first": 10, "lastId": lastID},
		OperationName: "PoolsPage",
	})
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest(http.MethodPost,
		"https://subgraphs.graphops.xyz/v1/alias/<ALIAS>", bytes.NewReader(payload))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("GRAPHOPS_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		log.Fatalf("HTTP %d: %s", resp.StatusCode, body)
	}

	var result struct {
		Data struct {
			Pools []struct {
				ID string `json:"id"`
			} `json:"pools"`
		} `json:"data"`
		Errors json.RawMessage `json:"errors"`
	}
	if err := json.Unmarshal(body, &result); err != nil {
		log.Fatal(err)
	}
	// GraphQL errors can arrive with HTTP 200; a missing page must not read as the last one.
	if result.Errors != nil || result.Data.Pools == nil {
		log.Fatalf("GraphQL errors: %s", body)
	}
	// The last id is the cursor for the next page; null means there are no more rows.
	var cursor *string
	if pools := result.Data.Pools; len(pools) > 0 {
		cursor = &pools[len(pools)-1].ID
	}
	next, err := json.Marshal(cursor)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(next))
}

```

Output

```json
"0x0005079292756443f2e711570f8ab4e1951d4f97"
```

Use this cursor as lastId in the next request

Rust · reqwest

Prerequisites

1. Install Rust.

2. Install reqwest, serde\_json, and tokio. `cargo add reqwest serde_json tokio -F reqwest/json -F tokio/macros -F tokio/rt-multi-thread`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

4. Replace `<ALIAS>` with your Endpoint Alias.

Tested with Rust 1.98.1, reqwest 0.13.5, serde\_json 1.0.145, tokio 1.48.0.

[Example project](/examples/subgraphs.guides.paginate-results.rust-6.tar.gz)

```rust
use serde_json::{json, Value};
use std::{env, error::Error, time::Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Start below every id. For the next page, pass the last id this page returns.
    let last_id = "0x0000000000000000000000000000000000000000";

    let response = reqwest::Client::new()
        .post("https://subgraphs.graphops.xyz/v1/alias/<ALIAS>")
        .bearer_auth(env::var("GRAPHOPS_API_KEY")?)
        .timeout(Duration::from_secs(30))
        .json(&json!({
            "query": r#"
        query PoolsPage($first: Int!, $lastId: ID!) {
          pools(
            first: $first
            where: { id_gt: $lastId }
            orderBy: id
            orderDirection: asc
          ) {
            id
            token0 { symbol }
            token1 { symbol }
          }
          _meta { block { number hash } }
        }
        "#,
            "variables": { "first": 10, "lastId": last_id },
            "operationName": "PoolsPage",
        }))
        .send()
        .await?
        .error_for_status()?;
    let result: Value = response.json().await?;
    let pools = result["data"]["pools"].as_array().ok_or("missing pools")?;
    // The last id is the cursor for the next page; null means there are no more rows.
    println!(
        "{}",
        pools.last().map_or(Value::Null, |pool| pool["id"].clone())
    );
    Ok(())
}

```

Output

```json
"0x0005079292756443f2e711570f8ab4e1951d4f97"
```

Use this cursor as lastId in the next request

Python · requests

Prerequisites

1. Install Python.

2. Install requests. `python -m pip install requests`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

4. Replace `<ALIAS>` with your Endpoint Alias.

Tested with Python 3.12.12, requests 2.34.2.

[Example project](/examples/subgraphs.guides.paginate-results.python-4.tar.gz)

```python
import json
import os

import requests

# Start below every id. For the next page, pass the last id this page returns.
last_id = "0x0000000000000000000000000000000000000000"

response = requests.post(
    "https://subgraphs.graphops.xyz/v1/alias/<ALIAS>",
    headers={
        "Authorization": f"Bearer {os.environ['GRAPHOPS_API_KEY']}",
    },
    json={
        "query": """
        query PoolsPage($first: Int!, $lastId: ID!) {
          pools(
            first: $first
            where: { id_gt: $lastId }
            orderBy: id
            orderDirection: asc
          ) {
            id
            token0 { symbol }
            token1 { symbol }
          }
          _meta { block { number hash } }
        }
        """,
        "variables": {"first": 10, "lastId": last_id},
        "operationName": "PoolsPage",
    },
    timeout=30,
)
response.raise_for_status()
pools = response.json()["data"]["pools"]
# The last id is the cursor for the next page; None means there are no more rows.
print(json.dumps(pools[-1]["id"] if pools else None))

```

Output

```json
"0x0005079292756443f2e711570f8ab4e1951d4f97"
```

Use this cursor as lastId in the next request

## Run the full scan

1. Send the first request with `$lastId` set before every `id`.
2. Process the page, then save the last `id` you processed.
3. Send the next request with that `id` as `$lastId`.
4. Stop when a page returns fewer rows than `$first`.

Keep these in mind for long scans:

* Keep page sizes bounded, and back off after `429` responses.
* Prefer `id_gt` filters over high `skip` values.
* Persist the last processed `id` so a worker can resume after a retry or restart.
* Later pages can reflect newer data than earlier ones. To read every page from the same block,
pass the same `block: { number: N }` argument on each query; otherwise log `_meta.block.number`
with each page.

## Next steps

* [Schemas and queries](../concepts/schemas-and-queries): Find the ordering fields and filters your schema generates.
* [Usage and limits](../usage-and-limits): Size pages and worker concurrency to your organization's limits.
* [Monitor sync health](./monitor-sync-health): Track indexing progress while a long scan runs.