or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

block-operations.mdclient-configuration.mdcomments.mddata-source-operations.mddatabase-operations.mderror-handling.mdfile-uploads.mdindex.mdoauth-authentication.mdpage-operations.mdpagination-helpers.mdsearch.mduser-management.md

pagination-helpers.mddocs/

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

```