SDK
Pagination
Cursor-based pagination and async iterators in the OptimoCMS TypeScript SDK.
Pagination
All list endpoints return paginated results with cursor-based pagination. The SDK provides two ways to work with this: manual cursors and async iterators.
How it works
Every paginated response contains a pagination object:
{
"data": [ ... ],
"pagination": {
"total": 47,
"limit": 20,
"nextCursor": "eyJpZCI6InBhZ2VfMjAifQ=="
},
"meta": { "requestId": "req_abc123", "timestamp": "2026-05-26T12:00:00Z" }
}| Field | Description |
|---|---|
total | Total number of items |
limit | Items per page (default 20, max 100) |
nextCursor | Cursor for the next page. null when there is no next page |
Method 1 — Manual cursors
Use list() with the cursor parameter to fetch page by page:
import cms from './cms';
// First page
const page1 = await cms.sites.list({ limit: 10 });
console.log(`${page1.data.length} of ${page1.pagination.total} sites`);
// Second page (if available)
if (page1.pagination.nextCursor) {
const page2 = await cms.sites.list({
limit: 10,
cursor: page1.pagination.nextCursor,
});
console.log(`Page 2: ${page2.data.length} sites`);
}Response page 1:
{
"data": [
{ "id": "site_001", "name": "Site 1", "status": "published" },
{ "id": "site_002", "name": "Site 2", "status": "published" },
{ "id": "site_003", "name": "Site 3", "status": "draft" }
],
"pagination": {
"total": 47,
"limit": 10,
"nextCursor": "eyJpZCI6InNpdGVfMDEwIn0="
},
"meta": { "requestId": "req_pg1", "timestamp": "2026-05-26T12:00:00Z" }
}Response last page:
{
"data": [
{ "id": "site_045", "name": "Site 45", "status": "published" }
],
"pagination": {
"total": 47,
"limit": 10,
"nextCursor": null
},
"meta": { "requestId": "req_pg5", "timestamp": "2026-05-26T12:00:02Z" }
}Fetch all items with a loop
import type { SiteSummary } from '@optimocms/sdk';
const allSites: SiteSummary[] = [];
let cursor: string | null = null;
do {
const response = await cms.sites.list({ limit: 100, cursor: cursor ?? undefined });
allSites.push(...response.data);
cursor = response.pagination.nextCursor;
} while (cursor);
console.log(`Total: ${allSites.length} sites`);Method 2 — Async iterator (recommended)
The SDK provides a listAll() method that automatically iterates through all pages:
// Iterate over ALL sites, regardless of how many pages there are
for await (const site of cms.sites.listAll()) {
console.log(`${site.name} — ${site.domain}`);
}The iterator fetches the next page when the current one is exhausted. You don't need to manage cursors yourself.
With filter parameters
// Only published pages
for await (const page of cms.pages.listAll('site_abc123', { status: 'published' })) {
console.log(`${page.title} (${page.slug})`);
}Early termination
// Stop after the first 5 results
let count = 0;
for await (const site of cms.sites.listAll()) {
console.log(site.name);
if (++count >= 5) break;
}Collect into an array
// All items in an array
const allPages = [];
for await (const page of cms.pages.listAll('site_abc123')) {
allPages.push(page);
}
console.log(`${allPages.length} pages found`);Pagination parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 20 | Items per page (1–100) |
cursor | string | — | Cursor from previous response |
orderBy | string | updatedAt | Sort field (pages only) |
direction | 'asc' | 'desc' | 'desc' | Sort direction (pages only) |
status | string | — | Filter by status |
Which endpoints support pagination?
| Endpoint | list() | listAll() |
|---|---|---|
cms.sites.list() | Yes | Yes |
cms.pages.list(siteId) | Yes | Yes |
cms.media.list(siteId) | Yes | Yes |
cms.shop.products.list(siteId) | Yes | Yes |
cms.shop.orders.list(siteId) | Yes | Yes |
cms.shop.coupons.list(siteId) | Yes | Yes |
cms.booking.bookings.list(siteId) | Yes | Yes |
cms.webhooks.list(siteId) | Yes | Yes |
cms.webhooks.deliveries(siteId, webhookId) | Yes | Yes |
cms.forms.submissions(siteId) | Yes | Yes |
cms.reviews.list(siteId) | Yes | Yes |
cms.recruitment.jobs.list(siteId) | Yes | Yes |
Example: bulk update with rate limit respect
import { OptimoCMS, OptimoCMSError } from '@optimocms/sdk';
const cms = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
maxRetries: 5,
});
async function updateAllPageTitles(siteId: string, prefix: string) {
let updated = 0;
for await (const page of cms.pages.listAll(siteId)) {
try {
await cms.pages.update(siteId, page.id, {
title: `${prefix} — ${page.title}`,
});
updated++;
console.log(`[${updated}] ${page.title} updated`);
} catch (error) {
if (error instanceof OptimoCMSError && error.status === 429) {
console.log('Rate limit reached, SDK retry handles this');
throw error;
}
console.error(`Error at ${page.id}: ${error}`);
}
}
console.log(`Done! ${updated} pages updated.`);
}Try it — curl equivalent:
# First page
curl "https://api.optimocms.com/v1/sites?limit=10" \
-H "X-Api-Key: your_api_key"
# Next page with cursor
curl "https://api.optimocms.com/v1/sites?limit=10&cursor=eyJpZCI6InNpdGVfMDEwIn0=" \
-H "X-Api-Key: your_api_key"Try it — MCP:
Get all pages from site site_abc123.
→ The MCP server automatically iterates through all pages.Next steps
- Error handling — Retry on 429 and server errors
- Authentication — Rate limits per tier
- API Reference — All endpoints