Pagination

Handle large result sets efficiently.

6 min read

Many Notion API endpoints return paginated results to handle large datasets efficiently. Instead of returning all results at once, the API returns them in "pages" with a configurable page size. The SDK provides convenient methods to work with paginated responses.

icon
Rate Limiting Consideration

When working with pagination, especially for large datasets, be aware that Notion's API has rate limits. Making too many requests in quick succession can result in rate limiting errors. This guide includes best practices for handling rate limits during pagination.

Which endpoints support pagination?

The following endpoints return paginated results:

  • - List all users in the workspace
  • - Query database entries
  • - List child blocks of a page or block
  • - List comments for a block or page
  • - Retrieve paginated page property items
  • - Search across pages and databases

Understanding pagination objects

PaginationRequest

Use to control pagination parameters:

<?php

use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$paginationRequest = new PaginationRequest();

$paginationRequest
    ->setPageSize(50)           // Number of items per page (default: 100)
    ->setStartCursor('cursor'); // Where to start (for subsequent pages)

Pagination results

All paginated endpoints return objects that extend :

  • - Returns if there are more pages available
  • - Returns the cursor for the next page (or if no more pages)
  • - Returns an array of resource objects for the current page

Basic pagination example

Here's a simple example that fetches the first page of users:

<?php

use Brd6\NotionSdkPhp\Client;
use Brd6\NotionSdkPhp\ClientOptions;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$options = (new ClientOptions())->setAuth('your_notion_token');
$notion = new Client($options);

// Fetch first page with custom page size
$paginationRequest = (new PaginationRequest())->setPageSize(10);
$userResults = $notion->users()->list($paginationRequest);

echo "Found " . count($userResults->getResults()) . " users on this page\n";
echo "Has more pages: " . ($userResults->hasMore() ? 'Yes' : 'No') . "\n";

if ($userResults->hasMore()) {
    echo "Next cursor: " . $userResults->getNextCursor() . "\n";
}

Complete pagination workflow

To retrieve all results across multiple pages, use a loop to iterate through all available pages:

<?php

use Brd6\NotionSdkPhp\Client;
use Brd6\NotionSdkPhp\ClientOptions;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;
use Brd6\NotionSdkPhp\Resource\User\AbstractUser;

$options = (new ClientOptions())->setAuth('your_notion_token');
$notion = new Client($options);

$allUsers = [];
$paginationRequest = new PaginationRequest();

do {
    // Make the API request
    $userResults = $notion->users()->list($paginationRequest);

    // Add current page results to our collection
    $allUsers = array_merge($allUsers, $userResults->getResults());

    echo "Fetched " . count($userResults->getResults()) . " users (Total: " . count($allUsers) . ")\n";

    // Prepare for next iteration
    if ($userResults->hasMore()) {
        $paginationRequest->setStartCursor($userResults->getNextCursor());
    }
} while ($userResults->hasMore());

echo "Finished! Retrieved " . count($allUsers) . " users total.\n";

// Now you can work with all users
/** @var AbstractUser $user */
foreach ($allUsers as $user) {
    echo "- " . $user->getName() . " (" . $user->getId() . ")\n";
}

Database query pagination

Database queries often return large result sets, making pagination essential:

<?php

use Brd6\NotionSdkPhp\Client;
use Brd6\NotionSdkPhp\ClientOptions;
use Brd6\NotionSdkPhp\Resource\Database\DatabaseRequest;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$options = (new ClientOptions())->setAuth('your_notion_token');
$notion = new Client($options);

$databaseId = 'your_database_id';
$allPages = [];

// Create database query request
$databaseRequest = new DatabaseRequest();

// Create pagination request with smaller page size for large databases
$paginationRequest = (new PaginationRequest())->setPageSize(25);

do {
    // Query the database
    $queryResults = $notion->databases()->query(
        $databaseId,
        $databaseRequest,
        $paginationRequest
    );

    // Collect results
    $currentPageResults = $queryResults->getResults();
    $allPages = array_merge($allPages, $currentPageResults);

    echo "Retrieved " . count($currentPageResults) . " pages from database\n";

    // Prepare for next page
    if ($queryResults->hasMore()) {
        $paginationRequest->setStartCursor($queryResults->getNextCursor());
    }
} while ($queryResults->hasMore());

echo "Total pages retrieved: " . count($allPages) . "\n";

Block children pagination

When working with pages that have many child blocks, pagination helps manage large content:

<?php

use Brd6\NotionSdkPhp\Client;
use Brd6\NotionSdkPhp\ClientOptions;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$options = (new ClientOptions())->setAuth('your_notion_token');
$notion = new Client($options);

$pageId = 'your_page_id';
$allBlocks = [];

$paginationRequest = new PaginationRequest();

do {
    // Get child blocks
    $blockResults = $notion->blocks()->children()->list($pageId, $paginationRequest);

    // Collect blocks
    $allBlocks = array_merge($allBlocks, $blockResults->getResults());

    echo "Retrieved " . count($blockResults->getResults()) . " blocks\n";

    // Prepare for next page
    if ($blockResults->hasMore()) {
        $paginationRequest->setStartCursor($blockResults->getNextCursor());
    }
} while ($blockResults->hasMore());

echo "Total blocks: " . count($allBlocks) . "\n";

Comment pagination

You can retrieve comments for a specific block or page.

<?php

use Brd6\NotionSdkPhp\Client;
use Brd6\NotionSdkPhp\ClientOptions;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$options = (new ClientOptions())->setAuth('your_notion_token');
$notion = new Client($options);

$blockId = 'your_block_id_with_comments';
$allComments = [];

$paginationRequest = new PaginationRequest();

do {
    // Get comments
    $commentResults = $notion->comments()->retrieve($blockId, $paginationRequest);

    // Collect comments
    $allComments = array_merge($allComments, $commentResults->getResults());

    echo "Retrieved " . count($commentResults->getResults()) . " comments\n";

    // Prepare for next page
    if ($commentResults->hasMore()) {
        $paginationRequest->setStartCursor($commentResults->getNextCursor());
    }
} while ($commentResults->hasMore());

echo "Total comments: " . count($allComments) . "\n";

Page property pagination

Some page properties, like , , or , can contain a large number of items and are therefore paginated.

<?php

use Brd6\NotionSdkPhp\Client;
use Brd6\NotionSdkPhp\ClientOptions;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$options = (new ClientOptions())->setAuth('your_notion_token');
$notion = new Client($options);

$pageId = 'your_page_id';
$propertyId = 'your_paginated_property_id'; // e.g., for a 'files' property
$allItems = [];

$paginationRequest = new PaginationRequest();

do {
    // Get property items
    $propertyResults = $notion->pages()->properties()->retrieve(
        $pageId,
        $propertyId,
        $paginationRequest
    );

    // Collect items
    $allItems = array_merge($allItems, $propertyResults->getResults());

    echo "Retrieved " . count($propertyResults->getResults()) . " property items\n";

    // Prepare for next page
    if ($propertyResults->hasMore()) {
        $paginationRequest->setStartCursor($propertyResults->getNextCursor());
    }
} while ($propertyResults->hasMore());

echo "Total items in property: " . count($allItems) . "\n";

Best practices

1. Choose appropriate page sizes

<?php

// For large datasets, use smaller page sizes to avoid timeouts
$paginationRequest = (new PaginationRequest())->setPageSize(25);

// For smaller datasets, larger page sizes are more efficient
$paginationRequest = (new PaginationRequest())->setPageSize(100);

2. Handle rate limits

When paginating through large datasets, you may encounter rate limits. Notion's API has rate limits that can be triggered during intensive pagination:

<?php

use Brd6\NotionSdkPhp\Exception\ApiResponseException;
use Brd6\NotionSdkPhp\Constant\NotionErrorCodeConstant;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$allResults = [];
$paginationRequest = new PaginationRequest();

do {
    try {
        $results = $notion->users()->list($paginationRequest);

        $allResults = array_merge($allResults, $results->getResults());

        if ($results->hasMore()) {
            $paginationRequest->setStartCursor($results->getNextCursor());
        }
    } catch (ApiResponseException $e) {
        if ($e->getMessageCode() === NotionErrorCodeConstant::RATE_LIMITED) {
            echo "Rate limited. Waiting before retry...\n";

            // Implement exponential backoff
            $delay = 60; // Start with 60 seconds
            sleep($delay);

            continue; // Retry the same request
        }

        // Handle other errors
        throw $e;
    }

    // Add a small delay between requests to be respectful
    usleep(100000); // 100ms delay

} while ($results->hasMore());

3. Implement smart delays

Add delays between pagination requests to avoid hitting rate limits:

<?php

use Brd6\NotionSdkPhp\Exception\ApiResponseException;
use Brd6\NotionSdkPhp\Constant\NotionErrorCodeConstant;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

function paginateWithRateLimit($notion, callable $requestCallback, int $delayMs = 100): array
{
    $allResults = [];
    $paginationRequest = new PaginationRequest();
    $retryCount = 0;
    $maxRetries = 3;

    do {
        try {
            $results = $requestCallback($paginationRequest);

            $allResults = array_merge($allResults, $results->getResults());

            if ($results->hasMore()) {
                $paginationRequest->setStartCursor($results->getNextCursor());
            }

            // Reset retry count on success
            $retryCount = 0;

            // Add delay between requests
            if ($results->hasMore()) {
                usleep($delayMs * 1000); // Convert ms to microseconds
            }

        } catch (ApiResponseException $e) {
            if ($e->getMessageCode() === NotionErrorCodeConstant::RATE_LIMITED) {
                $retryCount++;

                if ($retryCount > $maxRetries) {
                    throw new \Exception("Max retries exceeded for rate limiting");
                }

                // Exponential backoff: 60s, 120s, 240s
                $backoffDelay = 60 * pow(2, $retryCount - 1);
                echo "Rate limited. Waiting {$backoffDelay} seconds (attempt {$retryCount}/{$maxRetries})...\n";

                sleep($backoffDelay);
                continue; // Don't advance pagination, retry same request
            }

            throw $e; // Re-throw other exceptions
        }

    } while ($results->hasMore());

    return $allResults;
}

// Usage
$allUsers = paginateWithRateLimit($notion, function ($paginationRequest) use ($notion) {
    return $notion->users()->list($paginationRequest);
}, 200); // 200ms delay between requests

4. Handle errors gracefully

<?php

use Brd6\NotionSdkPhp\Exception\ApiResponseException;
use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$allResults = [];
$paginationRequest = new PaginationRequest();

do {
    try {
        $results = $notion->users()->list($paginationRequest);

        $allResults = array_merge($allResults, $results->getResults());

        if ($results->hasMore()) {
            $paginationRequest->setStartCursor($results->getNextCursor());
        }
    } catch (ApiResponseException $e) {
        echo "Error fetching page: " . $e->getMessage() . "\n";
        break; // Stop pagination on error
    }
} while ($results->hasMore());

5. Add progress tracking

<?php

use Brd6\NotionSdkPhp\Resource\Pagination\PaginationRequest;

$allResults = [];
$paginationRequest = new PaginationRequest();
$pageNumber = 1;

do {
    $results = $notion->users()->list($paginationRequest);

    $allResults = array_merge($allResults, $results->getResults());

    echo "Page {$pageNumber}: " . count($results->getResults()) . " items (Total: " . count($allResults) . ")\n";

    if ($results->hasMore()) {
        $paginationRequest->setStartCursor($results->getNextCursor());
        $pageNumber++;
    }
} while ($results->hasMore());
Did this answer your question?