SuiteScript 2.0 – “An unexpected SuiteScript error has occurred” with Array.sort()

Here is another coding challenge that set me back more time than I would have liked. Passing an array of objects to the Array.sort() function worked most of the time, but not always. And when it blew up, the error was completely unhelpful.

Please note this is server-side code. Client-side code runs in a different JavaScript engine. In my case it was running inside a Suitelet written in SuiteScript 2.0. I did NOT try this same problem in SuiteScript 2.1 which gets a completely new JavaScript engine and ES6. Perhaps it might have worked. Moving from 2.0 to 2.1 requires also updating libraries. That was too much work. Here was my basic problem:

var transactions = [
      salesrep: salesrep_id,
      salesorder_total: salesorder_total
   /* In my case, there were less than 200 
      records in this array when it failed.
      This was far short of the number of 
      records it processed successfully.
      The only thing I noticed about this 
      particular set of records which caused 
      the failure was the number of records 
      where salesorder_total was zero. 
      Not all, but more than a few! */

var sortedbyTotalDescending = transactions.sort(
      function(a, b) {
            return a.salesorder_total <= b.salesorder_total ? 1 : -1;

This worked correctly for months. However, there were certain times when it blew up. This is what solved the problem. And don’t ask me why. I’m clueless. It should have. Please direct your questions to NetSuite. I believe it is a bug in their SuiteScipt 2.0 JavaScript engine!

/* This one works! */
var sortedbyTotalDescending = transactions.sort(
      function(a, b) {
            if (a.salesorder_total == b.salesorder_total) return 0;
            return a.salesorder_total < b.salesorder_total ? 1 : -1;

SuiteScript – Modifying Addresses

Understanding how to edit addresses under a customer turned out to be a bit tricky. Here is how I did it.

First, you can add custom fields, but to do that, you need to go to

Customization >> Forms >> Address Forms

Customize the “Standard Address Form.” Assign a country or countries to your form. This is important if you plan to use custom fields in an address. The form is where you add them. Custom fields will then be available to addresses in the assigned countries.

Next, it is important to understand that addresses are a subrecord. The sublist is called “addressbook” and is indexed by “addressbookaddress”. This will make more sense when you review the code below. You read the sublist, and then the associated subrecord under it. Here are the standard fields in the sublist for “addressbook”.

Label – not unique and if omitted becomes Address Line 1
defaultbilling – must be unique to one address
defaultshipping – must be unique to one address
isresidential – can apply to any or all addresses

Under each address is a subrecord that contains these fields:

Address Line 1
Address Line 2
Address Line 3
Any custom fields that you’ve created

Here is a code example that demonstrates how to update addresses. The variable “line” corresponds with a zero-based index of the address as it appears in the Address tab under a customer. The variable “customer_id” is the internal ID of the customer and all assigned values are coming from variables which you’d set prior to running this code.

    * Load the record to gain access to the Address sublist 
    var rec = record.load({
        type: record.Type.CUSTOMER,
        id: customer_id,
        isDynamic: false

    if (/* you need to insert an address, do it here. You decide! */) {
            sublistId: 'addressbook',
            line: 0
        line = 0;

    * Modify those fields present in the Address sublist lines
        sublistId: 'addressbook',
        fieldId: 'label',
        value: label,
        line: line

        sublistId: 'addressbook',
        fieldId: 'defaultbilling',
        value: defaultbilling,
        line: line

        sublistId: 'addressbook',
        fieldId: 'defaultshipping',
        value: defaultshipping,
        line: line

        sublistId: 'addressbook',
        fieldId: 'isresidential',
        value: is_residential,
        line: line

    * Load the sublist record, which holds all the actual address fields
    var subrec2 = rec.getSublistSubrecord({
        sublistId: 'addressbook',
        fieldId: 'addressbookaddress',
        line: line

        * Modify all subrecord fields
        fieldId: 'attention',
        value: attention

        fieldId: 'addressee',
        value: addressee

        fieldId: 'addr1',
        value: addr1

        fieldId: 'addr2',
        value: addr2

        fieldId: 'addr3',
        value: addr3

        fieldId: 'city',
        value: city

        fieldId: 'state',
        value: state

        fieldId: 'zip',
        value: zip

    * Save the record

SuiteTalk 2021.2 Deprecating HMAC-SHA1

I recently got this in an email from NetSuite: “You are receiving this notification because you use the Token-based Authentication (TBA) feature in your account for integrations that use HMAC-SHA1 as a signature method. As of NetSuite 2021.2, the use of HMAC-SHA1 will be deprecated. Before your account is upgraded to 2021.2, you must change your integrations to use HMAC-SHA256 as the signature method.

It sounded ominous, but turned out not to be a huge effort. If you’re like me, this TBA stuff captured my attention just long enough to get it working. So getting back into it took a little refamiliarizing. Hopefully this will save you some time.

In my post SuiteTalk TBA Example in C#, you’ll find the following code snippet. Here are the changes required to move from HMAC-SHA1 to HMAC-SHA256. I commented out the old block to highlight what changed.

Problem: Leading Zeros in Server-Side JavaScript

I needed a method that would validate dates in multiple formats. Here are examples of dates that needed to pass validation: ‘6/9/2021’, ‘6/10/2021’, ‘9-Jun-2021′, ’10-Jun-2021′, ’06/09/2021′, ’06/10/2021’. This problem came to me on June 9th, 2021. So ’06/09/2021′ failed, but ’06/10/2021′ passed. Hmm… If you guessed it was not allowing backdates, you’d be wrong.

In my testing I learned that typeof 09 == ‘number’, but parseInt(09) = NaN. How can that be?

The NetSuite (server-side) JavaScript engine treats numbers with leading zeros as base-8 (octal). So be sure to strip leading zeros, even on integer numbers of type ‘number’. Since base-8 only included digits 0 through 7, parseInt(09) is not a number!

Replace NetSuite’s Native Rich Text Editor with TinyMCE

NetSuite’s native rich text editor has some problems. It often accepts or even creates HTML that NetSuite won’t print. Here’s how to replace it with TinyMCE, a rich text editor that works much better.

Start here, Marty Zigman’s wonderful article entitled, Replace NetSuiteā€™s Rich Text Editor with a Custom HTML Editor.

Now that you know all the steps, here’s my method. It’s much shorter and has fewer moving parts, but it breaks a few NetSuite rules regarding not editing the DOM.

It all begins with understanding what make’s NetSuite’s out-of-the-box rich text control work. Here is what the control looks like. In this case, I’m editing a custom fields called custbody_note_internal.

And here is the code that represents this control.

The rubber meets the road here:

<input id="custbody_note_internal" name="custbody_note_internal" type="hidden">

This is the control that is read back by the server to update the field in the underlying record. I found that if I swapped this type=”hidden” for a textarea control, the server accepted it just the same! And once you make the swap to a textarea, you can easily turn that textarea into a TinyMCE control.

Per Marty’s article above, you need to include the TinyMCE javascript library. He does this via client-side JavaScript and also adds code to wait for it to load. In my case (and I’m not showing this here), I load the reference along with other JavaScript libraries on the server-side. I download the link as part of the page. I’m still referencing the CDN delivered library. Because I’m delivering the link in the page and not dynamically via client-side JavaScript, I don’t need to wait for the library to load, which saves a step for me.

Next, anyplace client-side where I can run an after-load script tucked away inside a $(document).read() function, I place this code.

Let’s walk through this.

Line 1367: I’m getting a reference to multiple custom fields that all require a rich text edit swap to TinyMCE.
Line 1370: I’m getting the name and ID of the “Rubber meets the road hidden input field.”
Line 1371: I’m getting the initial value of the rich text editor I’m replacing.
Line 1372: I’m replacing the span tag on line 18 of the HTML with a textarea that has the name and ID of my custom field. It also has a class of “tinymce-richtext”.
Line 1379: I’m initializing TinyMCE on all my swapped textareas. I use the class to initialize them all at once.
Line 1384: Here is magic! This is an onchange event fired by TinyMCE. It copies the contents of the TinyMCE editor into the textarea that I created in line1372.

What’s amazing about this is that TinyMCE will convert pasted images into base-64 encoded html tags. These “Image” tags cut and paste just like regular text. so you can paste snippets of screens or any other image from your clipboard into the TinyMCE editor and not only will it save with the underlying record, it prints too!

This solution breaks NetSuite’s rule of not editing the DOM. However, it saves a number of steps outlined in Marty’s article. It comes with no guarantees. Use it at your own risk. And… be sure to test it with each new release of NetSuite. OK… That’s my final disclaimer. Good luck!

SuiteScript – Find Sublist Line by Value

This one eluded me. I needed to update Item Pricing records under a customer. So how to avoid iterating through every item in the sublist in order to find the line to update? Here’s the secret!

  • Load the customer using isDynamic = false (Standard Mode)
  • Lookup the line number of the line you need update using record.findSublistLineWithValue()
  • Then simply update the line just like you always do using the line number (zero based offset).

Here is a code example.

Here is the code in a format that you can cut and paste.

var customer = record.load({
    type: record.Type.CUSTOMER,
    id: kvp.customer_id,
    isDynamic: false

    function (pricing_item) {
        if (pricing_item.hasOwnProperty('unit_price')) {

            var lineNumber = customer.findSublistLineWithValue({
                sublistId: 'itempricing',
                fieldId: 'item',

            if (lineNumber >= 0) {
                    sublistId: 'itempricing',
                    fieldId: 'price',
                    line: lineNumber,
                    value: pricing_item.unit_price

SuiteScript – Accessing a Custom Record Type Sublist

This is documented in several NetSuite and non-NetSuite articles. I read them all and still found it almost impossible to decipher how to programmatically read a custom record type sublist.

Feel free to start with SuiteAnswers article 65795, or Google “Sublist recmach”, or whatever else you can think of… Or just read this! In my example I’m using two custom record types. The first is the parent of the second. The second is a sublist of the first.

Here is the parent custom record type. Nothing is important here except the name: customrecord_logic_software_agreements

Here is the child custom record type where it is very important to check the “Allow Child Record Editing.” Otherwise, nothing works! It is also vitally important to include a reference to the parent table. Here you see my reference to “Software Agreement.” Notice it is of type “List/Record” and the reference is to the custom record type “Software Agreements.”

Next, and also essential, is the definition of the foreign-key relationship between child and parent. In my case, custrecord_logic_sft_line_agreement. It is important to make sure to note that this field is the link to the parent record. You do that by checking “Record is Parent.”

Now, here is where the magic begins. The sublistId is the scriptId of the foreign-key field prefixed with “recmach”. Crazy, I know! But… It works!

I hope this saves you a lot of time. And more importantly, I hope this saves ME a lot lot of time the next time I want to programmatically reference a non-standard record type as a sublist.

Happy coding!

SuiteScript’s ‘N/Search’ filterExpression – Don’t forget it!

This came up yesterday. Here is my note regarding how it works.

If you create a search in SuiteScript 2.0, you can set a filter expression via the filters property.

var mySearch.create({
   type: [whatever type you like],
   columns: [ search.createColumn({ name: 'internalid'... }),
   filters: [
      ['internalid', 'anyof', [more filter stuff here]]

This works great. It accepts the advanced filter, no problem! However, when I load an existing saved search, I cannot submit a filter expression in the same way. Here’s how that works.

var mySearch.load({
   id: [an id of an existing saved search]
mySearch.filterExpression = [
   ['internalid', 'anyof' [more filter stuff here]]

When you load an existing search and want an advanced filter expression, you must use the filterExpression property!

Transactions may not reflect item setting changes

Here’s a gotcha! Changing a setting on a group item after you’ve added it to a transaction may not get reflected in your transaction going forward.

A perfect example of this is the setting “Display components on Transactions” checkbox in “item Groups”. If you print your transactions using a standard print template, you’ll probably want this checkbox unchecked. This prints just the group item, no subitems.

If you print your transactions using an Advanced PDF/HTML template, you will probably want this checked, so you can add up the total of all subitems and print it as the total for the group.

This become problematic when you switch from a standard print template to an advanced template. This is huge, so don’t miss it!!!!!

Any transaction that had that a group item added to it prior to changing that checkbox, remembers the previous setting. So copying an old transaction where the checkbox was checked yields the same setting on the line item in the copy. HUGE!!!

I’ve wasted way too much time tracking down group items that don’t print correctly after swapping from a basic print template to an advance print template. Hopefully this saves you and me both wasted time tracking this down in the future.

Custom Fields – Propagating Default Values

NetSuite’s ability to link custom fields and propagate default values is amazing. So much so, I wanted to make sure everyone following me knows how it works.

My task was to create a custom field, a dropdown at the customer level. Then add the same field to estimates and sales orders. When new estimates or sales orders are created, set the dropdown to the value of the customer assigned to the new transaction. However, allow the field to be modified after the fact. Then, when an estimate is copied to a sales order, let the modified selection follow to the new transaction. And, if the customer is changed, revisit the selection on same transaction and set it to the default value of the new customer. Crazy! I know.

Instead of writing complicated event scripts, this is easily accomplished when defining the custom field. Simple as that. Here’s the trick.

I added a custom dropdown at the customer level and populated from a list. Next, I created another custom field at the transaction level and assigned it to estimates and sales orders. It also populates from the same list. Here is how I sourced the custom field at the transaction level. It looks back to the customer to get its default value. Then it can be modified at any point thereafter. The most important thing to understand is that checking the “Store Value” checkbox, makes this behave as I’d described above.

Unchecking that checkbox will make this a “Calculated field.” In that case, it will always display whatever value is currently set in the dropdown at the customer level. This is a very important distinction!