At a Glance — n8n NetSuite Pagination

  • The loop: list call with limit and offset, then an IF node on hasMore, then offset plus page size, then back to the list call
  • Where the offset lives: workflow static data, written by small Code nodes before and after each page
  • Full export or delta: the same flow works with either of two list nodes. The delta version adds a q filter on lastModifiedDate and remembers the date of the last run
  • Why upsert: a date-based delta re-fetches records already synced earlier that day, so the Salesforce writes match on External_ID__c instead of creating new records
  • The catch: n8n only saves static data for a published workflow started by a trigger, not when you test by hand
  • Download: importable workflow JSON, with the credentials removed

The short answer: limit, offset, hasMore

NetSuite's REST record API returns lists one page at a time. For n8n NetSuite pagination, you call the list operation with a limit and an offset, look at the hasMore flag in the response, add the page size to the offset, and call again until hasMore is false. The n8n NetSuite community node's customer list operation accepts limit, offset and q (NetSuite's filter) as parameters, so the whole loop can be built from standard n8n nodes. If you haven't set the node up yet, start with our n8n NetSuite integration guide.

The only awkward part is remembering the offset between passes. n8n doesn't have a built-in "page counter" for custom loops, so this workflow keeps it in workflow static data, a small store the workflow can read and write from Code nodes.

How the NetSuite pagination loop works

The workflow is a daily NetSuite-to-Salesforce customer sync, but the pattern is the same for any record type. In order:

  1. Schedule trigger (Execute Workflow Daily) starts the run.
  2. Init Offset (Code) sets the offset to 0 and hasMore to true in static data.
  3. Has More Records? (IF) checks hasMore. True goes round the loop; false goes to the end.
  4. Retrieve Paging Offset and LastExportDate (Code) reads the offset and the last export date from static data.
  5. NetSuite list node fetches one page: limit 20, offset from the previous step.
  6. Split Customers Array turns the page's items into one item per customer.
  7. NS: Customer - Get record fetches each customer in full by ID, because the list call returns only IDs.
  8. Is Company? (IF) sends companies and individuals down different Salesforce paths.
  9. Salesforce upserts write the Account, and for individuals also the Contact.
  10. Update Paging Offset (Code) adds 20 to the offset, copies hasMore from the NetSuite response, and the flow returns to step 3.
  11. When hasMore is false, Update LastExportDate saves today's date and the run ends.

Each page therefore costs one list call plus up to 20 record calls. If you run into NetSuite request limits, lower the page size.

Keeping the NetSuite pagination offset in static data

Three small Code nodes manage the state. They use $getWorkflowStaticData('global'), which is shared by every node in the workflow.

Init Offset runs once at the start of each run:

// initialize staticData object
const workflowStaticData = $getWorkflowStaticData('global');

workflowStaticData.offset = 0
workflowStaticData.hasMore = true

return [
  {
      offset: workflowStaticData.offset,
      hasMore: workflowStaticData.hasMore
  }
];

Retrieve Paging Offset and LastExportDate feeds the list call. If no export date has been saved yet, it falls back to yesterday:

const workflowStaticData = $getWorkflowStaticData('global');
const lastExportDate = workflowStaticData.lastExportDate ? workflowStaticData.lastExportDate : $today.minus({days: 1}).toFormat('MM/dd/yyyy')

return [
  {
      offset: workflowStaticData.offset,
      lastExportDate: lastExportDate
  }
];

Update Paging Offset runs after each page. It moves the offset on and copies hasMore from whichever list node ran:

const workflowStaticData = $getWorkflowStaticData('global');

workflowStaticData.offset = workflowStaticData.offset+20
workflowStaticData.hasMore = $('NS: Customer - Get All records').isExecuted ? $('NS: Customer - Get All records').first().json.hasMore: $('NS: Customer - Get Delta records').first().json.hasMore

return [
  {
      offset: workflowStaticData.offset,
      hasMore: workflowStaticData.hasMore
  }
];
Static data only persists in a published workflow

n8n's documentation says it plainly: "Static data isn't available when testing workflows. The workflow must be published and called by a trigger or webhook to save static data." It also describes the feature as experimental and possibly unreliable under high-frequency executions. The saved export date only carries over from day to day once the workflow is published and running on its schedule. See n8n's static data documentation.

Full export or only what changed

The workflow contains two NetSuite list nodes, and you connect one of them between Retrieve Paging Offset and Split Customers Array:

The delta filter, as set in the node's q field:

lastModifiedDate ON_OR_AFTER "{{ $json.lastExportDate }}"

In the download, the delta node is the one that is connected. Two details to know:

Why the Salesforce writes are upserts

The saved date has no time in it, only a day. So a run that starts tomorrow asks for everything modified on or after the day of the last run, which includes records already synced earlier on that day. Handling a customer twice is therefore normal in this design, and the Salesforce writes have to cope with it.

That is what the upsert is for. Each Salesforce node uses the upsert operation with External_ID__c as the external ID field and the NetSuite customer ID as its value. The first time, Salesforce creates the record. After that, it updates it. Re-processing the same NetSuite customer never creates a second Account. (The same principle, make the write safe to repeat, is what stops duplicate actions in agent workflows; see race conditions in n8n agent workflows.)

NetSuite customers can be companies or individuals, and Salesforce models them differently, so the flow branches on isPerson:

What to change before you use it

This is a working pattern, not a finished product. Before pointing it at a real account:

Download the workflow

netsuite-customers-to-salesforce.json contains 18 nodes: the loop, both NetSuite list nodes (Delta connected, Get All not connected), the Salesforce branches, and sticky notes marking the NetSuite and Salesforce sections.

  1. In n8n, open the workflow menu and choose Import from file.
  2. Select the NetSuite and Salesforce credentials on each node. The file contains no credentials, only placeholders.
  3. Add the External_ID__c field in Salesforce.
  4. Connect the list node you want, set the schedule, and publish.

The NetSuite nodes use the n8n NetSuite community node (n8n-nodes-netsuite-rest), a verified community node that works on n8n Cloud and self-hosted n8n and authenticates with OAuth 2.0.

What was tested

This workflow was tested during development of the node. We have also checked the node settings in the file against the current release (0.1.15, whose code is unchanged from 0.1.14): the Customer resource, the get /customer/{id} operation, and the limit, offset and q parameters of the list operation all still exist. The downloadable file combines the delta and full-export variants of the same flow, with identifying details removed and the notes on the canvas tidied, so import it into a test instance first.

If you need a NetSuite-to-Salesforce sync that handles volume, errors and retries properly, Entech builds n8n and NetSuite integrations and can build and run it for you. See also our n8n Salesforce integration page.

Frequently asked questions

How do you paginate NetSuite REST results in n8n? Call the list operation with a limit and an offset, read the hasMore flag in the response, and loop until it is false, adding the page size to the offset after each pass. Keep the offset in workflow static data so it survives between loop iterations. The downloadable workflow does this with a page size of 20 and an IF node that checks hasMore.

Why store the offset in workflow static data? Workflow static data is a place the workflow can read and write between loop iterations and between runs, so the loop and the last-run date are remembered. n8n's docs say static data isn't available when testing workflows: the workflow must be published and started by a trigger or webhook for it to be saved, and the feature is described as experimental under high-frequency runs.

How do you sync only changed NetSuite records in n8n? Pass a q filter to the list call, for example lastModifiedDate ON_OR_AFTER with the date of the last run, and save that date at the end of each run. Because the filter is date-based, each run also re-fetches records already synced earlier on that date, so the destination write should be an upsert on an external ID, not a plain create.

Why use upsert instead of create when syncing NetSuite to Salesforce? An upsert on an external ID field such as External_ID__c creates the record the first time and updates it afterwards, so re-processing the same NetSuite customer never creates a duplicate. That matters whenever a run can overlap the previous one, which a date-based delta filter does by design.

Does the NetSuite community node for n8n work on n8n Cloud? Yes. n8n-nodes-netsuite-rest, built by Entech Solutions, is a verified community node, so it can be used on n8n Cloud as well as self-hosted n8n. It authenticates with OAuth 2.0 and its customer list operation supports limit, offset and q parameters.