0
# Pagination Helpers
1
2
Utility functions for handling paginated API responses with automatic cursor management.
3
4
## Capabilities
5
6
### Iterate Paginated API
7
8
Create an async iterator to automatically handle pagination for any paginated Notion API endpoint.
9
10
```typescript { .api }
11
/**
12
* Returns an async iterator over the results of any paginated Notion API
13
* @param listFn - A bound function on the Notion client that represents a paginated API
14
* @param firstPageArgs - Arguments for the first and subsequent API calls
15
* @returns Async iterator yielding individual results
16
*/
17
function iteratePaginatedAPI<T>(
18
listFn: (args: any) => Promise<PaginatedList<T>>,
19
firstPageArgs: any
20
): AsyncIterableIterator<T>;
21
22
interface PaginatedList<T> {
23
object: "list";
24
results: T[];
25
next_cursor: string | null;
26
has_more: boolean;
27
}
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { iteratePaginatedAPI } from "@notionhq/client";
34
35
// Iterate over all block children
36
for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
37
block_id: "parent-block-id",
38
})) {
39
console.log(`Block type: ${block.type}`);
40
// Process each block individually
41
}
42
43
// Iterate over all database pages
44
for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
45
data_source_id: "database-id",
46
filter: {
47
property: "Status",
48
select: { equals: "Published" },
49
},
50
})) {
51
console.log(`Page: ${page.properties.title?.title?.[0]?.text?.content}`);
52
}
53
54
// Iterate over all users
55
for await (const user of iteratePaginatedAPI(notion.users.list, {})) {
56
console.log(`User: ${user.name} (${user.type})`);
57
}
58
59
// Iterate with processing
60
const processedBlocks = [];
61
for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
62
block_id: "page-id",
63
})) {
64
if (block.type === "paragraph") {
65
processedBlocks.push({
66
id: block.id,
67
content: block.paragraph.rich_text[0]?.text?.content || "",
68
});
69
}
70
}
71
```
72
73
### Collect Paginated API
74
75
Collect all results from a paginated API into a single array.
76
77
```typescript { .api }
78
/**
79
* Collects all results from a paginated API into an array
80
* @param listFn - A bound function on the Notion client that represents a paginated API
81
* @param firstPageArgs - Arguments for the first and subsequent API calls
82
* @returns Promise resolving to array of all results
83
*/
84
function collectPaginatedAPI<T>(
85
listFn: (args: any) => Promise<PaginatedList<T>>,
86
firstPageArgs: any
87
): Promise<T[]>;
88
```
89
90
**Usage Examples:**
91
92
```typescript
93
import { collectPaginatedAPI } from "@notionhq/client";
94
95
// Get all block children at once
96
const allBlocks = await collectPaginatedAPI(notion.blocks.children.list, {
97
block_id: "parent-block-id",
98
});
99
100
console.log(`Total blocks: ${allBlocks.length}`);
101
allBlocks.forEach(block => {
102
console.log(`- ${block.type}`);
103
});
104
105
// Get all pages from a database
106
const allPages = await collectPaginatedAPI(notion.dataSources.query, {
107
data_source_id: "database-id",
108
sorts: [
109
{
110
property: "Created",
111
direction: "descending",
112
},
113
],
114
});
115
116
// Get all users in workspace
117
const allUsers = await collectPaginatedAPI(notion.users.list, {});
118
119
// Get all comments on a page
120
const allComments = await collectPaginatedAPI(notion.comments.list, {
121
block_id: "page-id",
122
});
123
124
// Collect with filters
125
const completedTasks = await collectPaginatedAPI(notion.dataSources.query, {
126
data_source_id: "tasks-database-id",
127
filter: {
128
property: "Status",
129
select: { equals: "Complete" },
130
},
131
});
132
```
133
134
## Comparison: Manual vs Helper Functions
135
136
### Manual Pagination
137
138
```typescript
139
// Manual pagination - more control but more code
140
async function getAllPagesManually(databaseId: string) {
141
let cursor: string | undefined;
142
let allPages: PageObjectResponse[] = [];
143
144
do {
145
const response = await notion.dataSources.query({
146
data_source_id: databaseId,
147
start_cursor: cursor,
148
page_size: 100,
149
});
150
151
allPages.push(...response.results);
152
cursor = response.next_cursor || undefined;
153
} while (cursor);
154
155
return allPages;
156
}
157
```
158
159
### Using Helper Functions
160
161
```typescript
162
// Using helper functions - cleaner and less error-prone
163
const allPages = await collectPaginatedAPI(notion.dataSources.query, {
164
data_source_id: databaseId,
165
});
166
167
// Or with async iteration for processing as you go
168
for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
169
data_source_id: databaseId,
170
})) {
171
// Process page immediately without storing all in memory
172
await processPage(page);
173
}
174
```
175
176
## Advanced Usage Patterns
177
178
### Memory-Efficient Processing
179
180
```typescript
181
// Process large datasets without loading everything into memory
182
async function processLargeDatabase(databaseId: string) {
183
let processedCount = 0;
184
185
for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
186
data_source_id: databaseId,
187
})) {
188
// Process each page individually
189
await performExpensiveOperation(page);
190
191
processedCount++;
192
if (processedCount % 100 === 0) {
193
console.log(`Processed ${processedCount} pages...`);
194
}
195
}
196
197
return processedCount;
198
}
199
```
200
201
### Filtered Collection
202
203
```typescript
204
// Collect only specific items
205
async function getRecentPages(databaseId: string, daysBack: number = 7) {
206
const cutoffDate = new Date();
207
cutoffDate.setDate(cutoffDate.getDate() - daysBack);
208
209
const recentPages = [];
210
211
for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
212
data_source_id: databaseId,
213
sorts: [{ property: "Created", direction: "descending" }],
214
})) {
215
const createdDate = new Date(page.created_time);
216
217
if (createdDate >= cutoffDate) {
218
recentPages.push(page);
219
} else {
220
// Since we're sorting by created date descending,
221
// we can break early once we hit older pages
222
break;
223
}
224
}
225
226
return recentPages;
227
}
228
```
229
230
### Parallel Processing with Batching
231
232
```typescript
233
// Process pages in parallel batches
234
async function processInBatches(databaseId: string, batchSize: number = 10) {
235
const pages = await collectPaginatedAPI(notion.dataSources.query, {
236
data_source_id: databaseId,
237
});
238
239
// Process in batches
240
for (let i = 0; i < pages.length; i += batchSize) {
241
const batch = pages.slice(i, i + batchSize);
242
243
// Process batch in parallel
244
await Promise.all(batch.map(page => processPage(page)));
245
246
console.log(`Processed batch ${Math.floor(i / batchSize) + 1}`);
247
}
248
}
249
```
250
251
## Type Guards for Paginated Results
252
253
Helper functions work with Notion's type guards for safer processing:
254
255
```typescript
256
import {
257
collectPaginatedAPI,
258
iteratePaginatedAPI,
259
isFullPage,
260
isFullBlock
261
} from "@notionhq/client";
262
263
// Safely process search results
264
const searchResults = await collectPaginatedAPI(notion.search, {
265
query: "project",
266
});
267
268
const pages = searchResults.filter(isFullPage);
269
const databases = searchResults.filter(result =>
270
result.object === "database"
271
);
272
273
// Process blocks with type safety
274
for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
275
block_id: "page-id",
276
})) {
277
if (isFullBlock(block) && block.type === "paragraph") {
278
console.log(block.paragraph.rich_text[0]?.text?.content);
279
}
280
}
281
```
282
283
## Rich Text Type Guards
284
285
Additional helper functions for type-safe handling of RichText items.
286
287
### Text Rich Text Type Guard
288
289
Checks if a rich text item is a text type.
290
291
```typescript { .api }
292
/**
293
* Type guard to check if richText is a TextRichTextItemResponse
294
* @param richText - Rich text item to check
295
* @returns true if richText is a text type
296
*/
297
function isTextRichTextItemResponse(
298
richText: RichTextItemResponse
299
): richText is RichTextItemResponseCommon & TextRichTextItemResponse;
300
```
301
302
### Equation Rich Text Type Guard
303
304
Checks if a rich text item is an equation type.
305
306
```typescript { .api }
307
/**
308
* Type guard to check if richText is an EquationRichTextItemResponse
309
* @param richText - Rich text item to check
310
* @returns true if richText is an equation type
311
*/
312
function isEquationRichTextItemResponse(
313
richText: RichTextItemResponse
314
): richText is RichTextItemResponseCommon & EquationRichTextItemResponse;
315
```
316
317
### Mention Rich Text Type Guard
318
319
Checks if a rich text item is a mention type.
320
321
```typescript { .api }
322
/**
323
* Type guard to check if richText is a MentionRichTextItemResponse
324
* @param richText - Rich text item to check
325
* @returns true if richText is a mention type
326
*/
327
function isMentionRichTextItemResponse(
328
richText: RichTextItemResponse
329
): richText is RichTextItemResponseCommon & MentionRichTextItemResponse;
330
```
331
332
**Usage Examples:**
333
334
```typescript
335
import {
336
isTextRichTextItemResponse,
337
isEquationRichTextItemResponse,
338
isMentionRichTextItemResponse
339
} from "@notionhq/client";
340
341
// Process rich text content with type safety
342
function processRichText(richTextArray: RichTextItemResponse[]) {
343
richTextArray.forEach(richTextItem => {
344
if (isTextRichTextItemResponse(richTextItem)) {
345
// Safe to access text-specific properties
346
console.log(`Text: ${richTextItem.text.content}`);
347
console.log(`Bold: ${richTextItem.annotations.bold}`);
348
console.log(`Link: ${richTextItem.text.link?.url || 'No link'}`);
349
} else if (isEquationRichTextItemResponse(richTextItem)) {
350
// Safe to access equation-specific properties
351
console.log(`Equation: ${richTextItem.equation.expression}`);
352
} else if (isMentionRichTextItemResponse(richTextItem)) {
353
// Safe to access mention-specific properties
354
console.log(`Mention type: ${richTextItem.mention.type}`);
355
if (richTextItem.mention.type === 'user') {
356
console.log(`User ID: ${richTextItem.mention.user.id}`);
357
}
358
}
359
});
360
}
361
362
// Extract plain text from rich text array
363
function extractPlainText(richTextArray: RichTextItemResponse[]): string {
364
return richTextArray
365
.filter(isTextRichTextItemResponse)
366
.map(item => item.text.content)
367
.join('');
368
}
369
370
// Find all mentions in rich text
371
function findMentions(richTextArray: RichTextItemResponse[]) {
372
return richTextArray.filter(isMentionRichTextItemResponse);
373
}
374
```
375
376
## Types
377
378
```typescript { .api }
379
interface PaginatedArgs {
380
start_cursor?: string;
381
}
382
383
interface PaginatedList<T> {
384
object: "list";
385
results: T[];
386
next_cursor: string | null;
387
has_more: boolean;
388
}
389
390
/**
391
* Returns an async iterator over the results of any paginated Notion API
392
*/
393
function iteratePaginatedAPI<T>(
394
listFn: (args: PaginatedArgs & any) => Promise<PaginatedList<T>>,
395
firstPageArgs: any
396
): AsyncIterableIterator<T>;
397
398
/**
399
* Collects all results from a paginated API into an array
400
*/
401
function collectPaginatedAPI<T>(
402
listFn: (args: PaginatedArgs & any) => Promise<PaginatedList<T>>,
403
firstPageArgs: any
404
): Promise<T[]>;
405
406
// Rich Text Types
407
type RichTextItemResponse = TextRichTextItemResponse | MentionRichTextItemResponse | EquationRichTextItemResponse;
408
409
interface RichTextItemResponseCommon {
410
type: string;
411
annotations: {
412
bold: boolean;
413
italic: boolean;
414
strikethrough: boolean;
415
underline: boolean;
416
code: boolean;
417
color: string;
418
};
419
plain_text: string;
420
href: string | null;
421
}
422
423
interface TextRichTextItemResponse {
424
type: "text";
425
text: {
426
content: string;
427
link: { url: string } | null;
428
};
429
}
430
431
interface EquationRichTextItemResponse {
432
type: "equation";
433
equation: {
434
expression: string;
435
};
436
}
437
438
interface MentionRichTextItemResponse {
439
type: "mention";
440
mention: {
441
type: "user" | "page" | "database" | "date" | "link_preview" | "template_mention";
442
user?: { id: string };
443
page?: { id: string };
444
database?: { id: string };
445
date?: { start: string; end?: string; time_zone?: string };
446
link_preview?: { url: string };
447
template_mention?: { type: string; template_mention_date?: string; template_mention_user?: string };
448
};
449
}
450
```