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
).
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 thefor
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.