Chidi Okwudire IT Professional. ERP Enthusiast. NetSuite Certified (Administrator, SuiteCloud Developer II, and ERP Consultant). Celigo Certified (Level 4+). Passionate About Empowerment Through Knowledge Sharing. Always Eager to Learn.

Learn How To Effectively Organize Your SuiteScript Utility Functions

5 min read

TL;DR

It is a best practice to organize reusable functions into a utilities module. Such a module may include pure JavaScript functions as well as functions that use NetSuite’s APIs/modules. However, some NetSuite modules work only in client scripts or server scripts, making it impossible to include all utilities in a single JavaScript file. The 3-file pattern explained in this article will ensure that you maximize reusability of your utility functions across all scripts. To get you going, we’ve provided a template for the 3-file utility pattern which you can download here.

Problem

Larry, the lead developer at Asoville Inc., is faced with a situation. Over the years, he’s created a collection of handy functions that he reuses across many of his NetSuite scripts. He’s put these functions in a GenUtils.js file which he includes in other scripts as needed. As illustrated below, this file contains pure JavaScript functions (lines 12 – 18) as well as functions that use NetSuite’s modules (lines 20 – 41).

/**
 * @file General utility functions.
 *
 * @NApiVersion 2.0
 * @NModuleScope Public
 */

define(['N/error', 'N/runtime', 'N/url'],

function(error, runtime, url) {

    function removeWhiteSpaces(str){
       return (str || '').replace(/\s/g, '');
    }

    function toSpacelessUppercase(str) {
        return removeWhiteSpaces(str).toUpperCase();
    }

    function getAccountDomain() {
        return url.resolveDomain({
            hostType: url.HostType.APPLICATION,
            accountId: runtime.accountId
        });
    }

    function generateRecordLink(recordType, recordId, linkText) {
        if (recordType == null || recordId == null) {
            throw error.create('Cannot generate record link. Invalid inputs');
        }
        const recordUrl = url.resolveRecord({
            recordType: recordType,
            recordId: recordId
        });

        if (!linkText) {
            linkText = recordId;
        }

        return '<a href="https://' + getAccountDomain() + recordUrl + '">' + linkText + '</a>';
    }

    return {
        removeWhiteSpaces: removeWhiteSpaces,
        toSpacelessUppercase: toSpacelessUppercase,

        getAccountDomain: getAccountDomain,
        generateRecordLink: generateRecordLink
    }
});

This approach has worked very well until recently when he added a new utility for working with NetSuite files. Suddenly, his client scripts that depend on GenUtils.js started giving the following error.

Fail to evaluate script: {
    “type”: ”error.SuiteScriptModuleLoaderError”,
    ”name”: ”MODULE_DOES_NOT_EXIST”,
    ”message”: ”Module does not exist: N / file.js”,
    ”stack”: []
}

(On an unrelated note, this error may also be caused by failing to include the .js extension at the end of your JavaScript file names as explained here.)

Larry searched in SuiteAnswers and found Answer Id: 43517 which explains that not all NetSuite modules work in both client and server-side scripts. Therefore, trying to include a server-only module like N/file in a Client Script is not allowed. Similarly, client-side modules like N/ui/dialog will not work in server-side scripts (e.g. User Event, Scheduled or Map Reduce Script).

Note that the above error will generally occur when adding a server-only module in a client script and trying to save the JavaScript file via the NetSuite UI. From my tests, adding a client-only module to a server-side script did not produce any errors. However, since the module is not available, the code will misbehave. Similarly, when pushing files via SDF, the error might not occur. The key takeaway is that you should not rely on the above error message but instead know which modules can be used where.

As a SuiteScript Developer, you should know which NetSuite modules are supported on client side, server side or both. Do not depend on the “MODULE_DOES_NOT_EXIST” as it might not be shown in all contexts, possibly leading to harder-to-detect logical errors in your scripts.

NetSuite Modules And Where They Can Be Used

As at the time of writing, here’s the list of all NetSuite modules indicating where they can be used [I]NetSuite (August 7, 2015). SuiteScript 2.0 Modules. Available at https://netsuite.custhelp.com/app/answers/detail/a_id/43517 [Accessed November 30, 2020]. Clearly, the server-only modules are by far the most. There are currently just 4 client-only modules which is why chances are that you might not directly encounter the error described above until you start doing more client-side work.

Client ModulesServer ModulesClient & Server Modules
N/currentRecord
N/portlet
N/ui/dialog
N/ui/message
N/auth
N/cache
N/certificateControl
N/compress
N/config
N/crypto
N/crypto/certificate
N/dataset
N/encode
N/error
N/file
N/https/clientCertificate
N/keyControl
N/piremoval
N/plugin
N/redirect
N/render
N/sftp
N/task
N/task/accounting/recognition
N/ui/serverWidget
N/workbook
N/workflow
N/action
N/commerces
N/currency
N/email
N/format
N/format/i18n
N/http
N/https
N/log
N/query
N/record
N/recordContext
N/runtime
N/search
N/sso
N/transaction
N/translation
N/url
N/util
N/xml
An Overview of NetSuite Modules And Where They Can Be Used as Per Answer Id: 43517

Clear. Larry now understands the issue. The question though is how can he tackle this challenge? The naïve approach would be to create two utility files, one to be included in client-side scripts and the other to be included in server-side scripts. But what about utilities that work in both contexts? Duplicating such functions in both utility files is certainly not a good idea as it increases maintenance and violates the reusability principle which is a bedrock of good software development.

There has to be a better way…


Solution

The solution is surprisingly simple: Instead of using two utility files, Larry will need three files as illustrated below:

NetSuite Utils 3-File Pattern
The 3-File Utilities Pattern Illustrated
  • CommonUtils.js contains all pure JavaScript utility functions as well as all NetSuite utility functions that use modules which are available on both client- and server-side.
  • ServerUtils.js contains only utilities that use server-only NetSuite modules e.g. N/file. In addition, it exposes all the utilities defined in CommonUtils.js (I’ll show you how in a moment).
  • ClientUtils.js contains only utilities that use client-only NetSuite modules e.g. N/ui/dialog. Similar to ServerUtils.js, it includes all the utilities defined in CommonUtils.js

With this structure in place, Larry is back in business. His server-side scripts will simply import ServerUtils.js and his client-side scripts will use ClientUtils.js. Notice that there is never a need to directly import CommonUtils.js as the contents of this script are exposed via the other utility files as illustrated below:

/**
 * @file Sample Server Utilities module.
 *
 * @NApiVersion 2.0
 * @NModuleScope Public
 */

define(['N/file', './CommonUtils.js'],

function(file, commonUtils) {

    // Returns a file URL if the file is accessible without login.
    // If not, returns null.
    function getFilePublicUrl(fileId) {
        if (fileId == null) {
            return;
        }
    
        var fileObj = file.load({
            id: fileId
        });
        
        if (fileObj.isOnline) {
            return fileObj.url;
        }
    }
    
    var exports = commonUtils; // loads all common utils in one go!
    exports.getFilePublicUrl = getFilePublicUrl;

    return exports;
});

On line 28, the exports variable is initialized to the commonUtils module. This simple line does the trick of exposing all the functions in commonUtils! Subsequently, we add any new functions from the current module to the exports object to expose them. The exports object returned on line 31 now contains all utilities from CommonUtils.js and ServerUtils.js. The pattern is exactly the same for ClientUtils.js so I’ll skip that.

Avoid Unnecessary Work!

A common mistake I’ve seen among less experienced developers is that, although they apply the 3-file pattern, they still manually import the utility functions defined in CommonUtils.js into their ServerUtils.js and ClientUtils.js as illustrated below.

/**
 * @file Sample Server Utilities module (the not-so-clever approach).
 *
 * @NApiVersion 2.0
 * @NModuleScope Public
 */

define(['N/file', './CommonUtils.js'],

function(file, commonUtils) {

    // Returns a file URL if the file is accessible without login.
    // If not, returns null.
    function getFilePublicUrl(fileId) {
        if (fileId == null) {
            return;
        }
    
        var fileObj = file.load({
            id: fileId
        });
        
        if (fileObj.isOnline) {
            return fileObj.url;
        }
    }
    
    return {
        // Functions from commonUtils. (AVOID this redundancy!!!)
        removeWhiteSpaces: commonUtils.removeWhiteSpaces,
        toSpacelessUppercase: commonUtils.toSpacelessUppercase,
        getAccountDomain: commonUtils.getAccountDomain
        generateRecordLink: commonUtils.generateRecordLink
        
        // Functions from this module
        getFilePublicUrl: getFilePublicUrl
    }
});

This approach, while addressing the reusability concern, still incurs an unnecessary (huge) maintenance penalty (lines 29 – 33): Each time a new function is added to CommonUtils.js, it has to also be manually added to ClientUtils.js and ServerUtils.js in order to expose it. That extra work is a waste of time and should be avoided by adopting the approach described earlier in which we load all functions exposed in commonUtils in one go.

Best Practice: Use The 3-File Utilities Pattern From The Onset

I recommend that you use the 3-file pattern from the onset even if you do not yet have any server-only or client-only utilities. In this way, you’ll not need to do any refactoring later on. Clearly, most of your utilities will be used on server-side or work in both contexts. However, the N/currentRecord and N/ui/dialog, which are only available on client side, are quite commonly used. So chances are that, sooner or later, you’ll produce a utility that depends on one of these modules. It is better to be prepared.

If you already have a utilities module and want to migrate to this pattern, it should be pretty straightforward: Everything generic stays in your CommonUtils.js, the rest is moved to ServerUtils.js or ClientUtils.js. By analyzing the NetSuite modules imported by your currently utilities file, you can easily tell what should go where. To get you going, we’ve provided the 3-file template for your use:

Get the 3-file utilities template from NetSuite Insights here and start organizing your utility functions properly today.

Parting Words

The 3-file pattern for managing utilities is a very simple yet powerful approach. If you’ve been doing this all along, it will feel very natural and obvious. However, I’m sure there are many developers out there who need to know this. Personally, when I first encountered this problem some years back, the solution was not obvious to me (I fell into the “unnecessary work” trap of manually exposing each function from my commonUtils). This is one of those little things that I wish someone had told me then. Now you know, it’s up to you to adopt it and spread the word.


If you’re a SuiteScript developer, Eric T. Grubaugh, a.k.a. “the SuiteScript guy”, has an email newsletter in which he regularly shares various SuiteScript patterns that will help you become a stronger SuiteScript developer. You can subscribe to his newsletter here to stay in the know and rub minds with others.

Ignorance is sometimes a choice. Choose wisely!

Further Reading[+]

Chidi Okwudire IT Professional. ERP Enthusiast. NetSuite Certified (Administrator, SuiteCloud Developer II, and ERP Consultant). Celigo Certified (Level 4+). Passionate About Empowerment Through Knowledge Sharing. Always Eager to Learn.

Leave a Reply

Your email address will not be published. Required fields are marked *

×