Expand your SuiteScript skills in the Sustainable SuiteScript community for NetSuite developers.

SuiteScript Comparison - Setting Sublist Lines to Zero

Created: March 28, 2025

NetSuite's documentation team maintains a repository of SuiteScript examples which contains a ton of useful information with code samples.

In the Sustainable SuiteScript Slack community, I've gradually been going through these examples and publishing my own version of each one in what I'm calling "Side-by-Sides".

This article walks through my side-by-side comparison of the set-item-amount-to-zero example.

The Overview

NetSuite summarizes the purpose of this example like so:

This script determines if the Sales Order being saved is a Marketing Order. If it is a Marketing Order, the script sets the Amount field on the sales order to zero (0).

Here, a "Marketing Order" is a Sales Order with a specific custom body checkbox (custbody_marketing_order) checked (set to true).

  • NetSuite's code is here.
  • My code is here.
  • NetSuite describes this example in great detail here.

The Comparison

This example illustrates two concepts that are I think especially important for SuiteScript beginners:

afterSubmit versus beforeSubmit

First, we really want to avoid using afterSubmit to modify the record that the User Event was triggered by (i.e. the record getting passed in via context).

Doing so means the record gets committed to the database twice (at least) every time it is saved. We want to avoid that duplication wherever possible. Sometimes, it is unavoidable, but that should be the rare exception, not the rule.

Whenever possible, we want to use beforeSubmit to modify the record in context. NetSuite themselves recommends this in their User Event Best Practices article:

To set a field on a record or make any changes to a record being submitted, use the beforeSubmit event.

Avoid record.load() whenever possible

Second, N/record.load() is one of the more expensive operations you can do in SuiteScript. There are lots of ways to retrieve the information from a Record; load-ing the whole thing should generally be the last option. In this example, the Record was being loaded because the code was subsequently modifying and saving it as well. Since I removed that latter part, there was no longer a need to load the Record, and we can instead grab the data we need from the Record instance being given to us in memory.

Let's pretend that for some reason, we had to use afterSubmit for this, which can be the case in real-world situations. I would still replace the retrieval of the Marketing Order field with lighter options than loading the whole record.

My first approach would be to retrieve the value directly from the record in memory, which is exactly what I do in my code here:

if (isMarketingOrder(context.newRecord)) {
  // ...
}
// ...
const isMarketingOrder = (rec) => rec.getValue({ fieldId: 'custbody_marketing_order' })

If that wasn't going to work for some reason, my next approach would be to use search.lookupFields() to retrieve the necessary field values. lookupFields runs much faster and consumes only 1 unit of governance instead of the 10 consumed by loading a transaction.

In that case, because I isolated the retrieval to the isMarketingOrder function, I only need to change the code there, and nowhere else:

const isMarketingOrder = (rec) => search.lookupFields({
  ...rec,
  columns: ['custbody_marketing_order']
})?.custbody_marketing_order

Even if our code might eventually load() the record, I want to put that off as long as possible, and only perform the load() when it's actually necessary.

For this code, load() is only necessary when the transaction is a Marketing Order, but this User Event runs every time any Sales Order is saved, so I want to make that "Is this a Marketing Order?" determination as quickly and lightly as possible, without loading the record.

This becomes especially true if Marketing Orders are the minority of Sales Orders in this account.

If this code naively loaded the record every time, and Marketing Orders were only 10% of Sales Orders, then (roughly) 90% of the time a Sales Order was saved, we'd be unnecessarily performing a transaction load() for absolutely no reason.

Fixed iteration with Array.from()

As a bonus, during the creation of this example, I learned a new technique for iterating a fixed number of times, as we often want to do in SuiteScript when iterating over a sublist:

const zeroItemAmounts = (rec) => {
  const length = rec.getLineCount({ sublistId: 'item' })
  const zeroAmount = { sublistId: 'item', fieldId: 'amount', value: 0 }

  Array.from({ length })
    .forEach((_, line) => rec.setSublistValue({ ...zeroAmount, line }))
}

I learned this technique in the answers and comments of this Stack Overflow question.

Aside: I went looking for an alternative to the for loop because my personal preference is to avoid them; there's nothing inherently wrong with the for loop; if you like it, keep it.

Using the Array.from() method, we can generate an Array of a specific length like so:

Array.from({ length: 10 })
// where 10 is an arbitrary placeholder for however many times we want to iterate

This creates an Array of the given length where each element is undefined. In my case, I use the length of the item sublist to set the Array size.

const length = rec.getLineCount({ sublistId: 'item' })
// ...
Array.from({ length })

From there, we could use any of the typical Array iteration methods - a for loop, a for..of loop, forEach(), map(), etc. I chose to use forEach() to set the same value on each line:

Array.from({ length })
  .forEach((_, line) => rec.setSublistValue({ ...zeroAmount, line }))

Alternatively, Array.from() also provides a second parameter, which acts the same as a map() over each element. I did use this initially, and it works. I opted for the forEach instead since I'm not actually mapping the Array elements to new values for use later; forEach felt more semantically correct.

One potential downside of this approach, as pointed out in one the Stack Overflow comments, is that we're creating an empty Array just to throw it out, which is potentially a waste of memory.

I don't know enough about NetSuite's JavaScript runtime and how it might interpret, optimize, and garbage collect this "empty" Array to give specific details or limits to this approach.

If I were working with thousands of iterations, I'd avoid this approach to prevent any potential memory blockage.

In this case, where we're likely working on the order of tens of lines, I believe the memory usage should be negligible, so I'm OK potentially using a little more memory than I need and trusting the engine to clean up in short order.

When you start writing SuiteScript for large scale operations, though, these are the kinds of considerations you need to make in order to be an effective developer and a good tenant on the shared environment that is NetSuite.