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.
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 requests4. 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());