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 token
export 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: transient is required on every SuiteQL REST call.
  • id must be in the SELECT list, and the table must have a numeric id.
  • Only interpolate values you control. For user input, validate it as a number or a known enum first.