- Published on
SuiteQL keyset pagination
The REST SuiteQL endpoint pages with limit and offset, up to 1,000 rows per page. Offset paging stops working past 100,000 rows. Keyset pagination (WHERE id > lastId ORDER BY id) has no cap, and it does not skip or repeat rows when records change during the run.
suiteql-keyset.js
// getAccessToken: any function that returns a valid OAuth 2.0 bearer tokenexport async function* suiteqlKeyset({ host, getAccessToken, select, from, where = '1 = 1', pageSize = 1000 }) { const url = `https://${host}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql?limit=${pageSize}` let lastId = 0
while (true) { const res = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${await getAccessToken()}`, 'Content-Type': 'application/json', Prefer: 'transient', }, body: JSON.stringify({ q: `SELECT ${select} FROM ${from} WHERE (${where}) AND id > ${lastId} ORDER BY id`, }), }) if (!res.ok) throw new Error(`SuiteQL ${res.status}: ${await res.text()}`)
const { items } = await res.json() if (items.length === 0) return yield items lastId = Number(items[items.length - 1].id) }}Usage:
for await (const page of suiteqlKeyset({ host: '1234567-sb1', getAccessToken, select: 'id, tranid, trandate, foreigntotal', from: 'transaction', where: "type = 'SalesOrd' AND trandate >= TO_DATE('2026-01-01', 'YYYY-MM-DD')",})) { await saveToWarehouse(page)}Notes:
Prefer: transientis required on every SuiteQL REST call.idmust be in theSELECTlist, and the table must have a numericid.- Only interpolate values you control. For user input, validate it as a number or a known enum first.