The commerce extension framework enables you to create extensions that plug into your web store, making it easy for developers to customize web stores and update the core code with little fuss. However, in order for that to be successful, developers should follow best practices to ensure customizations can be moved between sites and versions.

In some ways, this is a follow-on post to The Extension Framework and Extensibility API, which I would recommend reading first.

Before Approaching Customizations

I would also be remiss if I didn’t preface a document like this with the wild suggestion that perhaps you don’t need a customization or extension. What I mean by that is: is there a way you can do this without introducing new code?

Can you do what you want to with configuration rather than customization?

The reason I emphasize this question is because, over the releases, we have made improvements to the tools available to users to introduce change to their site without the need to intervene on a code-level. Adding code-level changes will always introduce a level of fragility to the customization, as well as make it difficult for non-technical users to make future changes.

For more information, see Use Configuration Instead of Customization.

Customizations in SuiteCommerce vs SuiteCommerce Advanced

The differences in customization methods between these two editions can be a useful prism through which to understand best practices for writing extensions. Many developers have experience working with SuiteCommerce Advanced, especially historical versions, but techniques and best practices have changed since early releases and the differences in what the two editions prioritize are significant.

SuiteCommerce is NetSuite's commerce offering that is truly software-as-a-service (SaaS). The principle idea is that a site can be customized but still migrated to a new version automatically, whenever an update becomes available. Conversely, SuiteCommerce Advanced, allows for customizations to be introduced much intimately into the code’s bundle, including interacting with core code modules and changing what modules are included in the final build package.

The extension framework aims to separate customizations from the source code with a code layer — the extensibility API — and a framework for activating customizations so that they are automatically combined, as much as is necessary, into your site's final code. Importantly, however, extensions always live as a layer on top of the source code — this means that they should be additive enhancements, rather than modifications to core behavior.

There a numerous other differences and similarities between editions of NetSuite Commerce, but for the purposes of customizations, we should focus on the following:

FactorSuiteCommerceSuiteCommerce Advanced
Offering StyleSoftware-as-a-servicePlatform-as-a-service
ExtensionsYesAconcagua or newer
Extensibility APIYesAconcagua or newer
Code-Level CustomizationExtensions and themesExtensions, themes and full source
Migration to Newer VersionsAutomatic, always up-to-dateManual, when you decide to

There has been some confusion about whether the extension framework — the API, extensions and extension manager — is available for SCA and the answer is yes. If you're working on a modern version of SCA then the customizations available to SuiteCommerce are also available to you; the principle difference is that you are also given full access to the site's source code, so you can make more complex customizations if you wish. The extensibility API is available throughout the SCA source code (ie not just extensions). Also, depending on your versions of SCA, some newer components and methods of the API may not be available to your version.

Their similarities and differences are discussed in more detail in The Technical Differences Between SuiteCommerce and SuiteCommerce Advanced, but I highlight two key points:

  • The SC source code bundle is locked, but the SCA is unlocked
  • The SC source code bundle is automatically updated, the but the SCA is manually updated (or not updated at all)

Accordingly, any customizations made to the core code bundle of a SuiteCommerce Advanced site can become ‘locked in time’ if the bundle is never updated. If the owner wants to move it to a new version, they would have to migrate the customizations as well. Sometimes, that can be simple, and sometimes it can be difficult.

Some SCA site owners think they are happy for their site to be version-locked because they want pixel perfection and very specific customizations. However, we caution against this: while they may be happy at the time to get exactly what they need, after a few years many express remorse that they are no longer able to take advantage of the latest improvements in newer releases.

For SuiteCommerce sites, this is not really an option: your site’s core bundle will always be regularly updated. Accordingly, there is no way to lock a site’s source to a specific version and any customization that depends on a specific version of code will become fragile and risks breaking when the core bundle is upgraded.

Furthermore, if you a partner or customization developer for a wide range of sites then there is a secondary dimension you need to keep in mind: the effects your customizations will have on other sites. When writing a customization for distribution to more than one site, you must take into account the effect it will have on sites running their own customizations.

Core Values of SuiteCommerce Sites

Core values or priorities are the things that are considered most important.

As extensions can be applied to both SuiteCommerce and SuiteCommerce Advanced sites, we recommend always following the strictest of practices — that is, even if you are coding for an SCA site, we would recommend treating any extension you write for it as if you were coding it for a SuiteCommerce site.

With that in mind, you should notice what some of the differences are between the two editions. Specifically, SuiteCommerce sites prioritize:

  1. Upgradeability — they are automatically migrated to newer versions
  2. Reusability — they have an identical code base across all sites
  3. Maintainability — they want to minimize customizations that are time-consuming to maintain

There is a level of overlap between them but let’s break them down.

Firstly, upgradeability means that a site or customization is easy to keep current. A poorly written extension can interfere or block a site’s core code upgrade by breaking the site (eg by throwing errors, blocking checkout, or otherwise malfunctioning). A well written extension will continue to work as expected.

Next, reusability: this means using standardized components, frameworks and technologies that are common across all sites. A poorly written extension relies on components that are specific to a particular site, or does not account for scenarios where a site may have disabled certain bits of functionality or made a configuration change. A well written one will account for the idiosyncrasies every site might have by ensuring it checks features or configurations are available before trying to use them.

Finally, we have the concept of maintainability. Part of this covers how much time a developer must spend working on an extension or site to keep it working smoothly, but also minimizing the cost for site operators who might have to troubleshoot problems or hire technical people to fix customizations.

Key Principles of Stability and Sustainability

Now that we understand what our core values are, we should then look at how we can create guiding principles. Based on that, we can extrapolate that developers must:

  • Avoid customizations that could break when a site’s core code is upgraded
  • Depend exclusively on a stable code foundation (ie the extensibility API)
  • Minimize risks that require intervention from a technical people, such as developers or support staff

Let’s look at some ways to abide by those principles.

Avoid Version-Lock with Version-Agnostic Code

Version-agnostic customizations are those that don’t ‘care’ what version of SuiteCommerce or SuiteCommerce Advanced they are installed on to. A customization that does depend on a module, class or method behaving in a way specific to a certain point in time is said to ‘version-lock’ the site because it creates a deadlock:

  • The site cannot be updated until the extension is updated first
  • The site is updated but the extension will be uninstalled first

This is particularly important for the software-as-a-service SuiteCommerce product as its core code is automatically updated every release. Problems can arise if you write a customization that relies on core code modules as there is no guarantee that it will continue working the same across releases. If this happens on your live site, this could have serious ramifications on its ability to trade and serve customers.

To give a historical example, consider the refactoring and renaming of the SuiteCommerce Advanced source code in Elbrus that changed how we handle product data, specifically the product detail page. If, before that time, you made customizations that relied on the ItemDetails module, you would have had to completely rewrite them because the class names you depended on no longer existed when you migrated to this release. This is because Elbrus split up the functionality into other modules, such as Item and ProductDetails.

In more recent times, version-locked sites have arisen because developers have used code classes from outside the extensibility API and have depended on them in a certain way. When NetSuite developers changed their behavior, it created the deadlock.

Depend Entirely Upon the Extensibility API

The extensibility API contains the only classes, methods, and events of the SuiteCommerce code bundle that we can say with a strong confidence will behave consistently across versions and sites. Calls to core code modules in SuiteCommerce extensions (ie like you would in SuiteCommerce Advanced customizations) must be avoided.

Avoid Using Core Modules in Customizations

The other side of the “depend entirely upon the extensibility API” coin is that this means you should not use SuiteCommerce core modules in your extensions. SuiteCommerce Advanced developers may use them (responsibly) in their core code customizations, but they should not be used on SuiteCommerce sites or extensions.

Best practices around using core modules in customizations, particularly extensions, have changed over time. However, from SC/SCA 2020.2.1, we are now strongly discouraging using any core code module in extensions. In some cases, access to these modules will be actively blocked and an error will be shown in the developer console; in other cases, the call will be allowed and a warning will be shown instead. Over time, more modules will be added to the ‘blocklist’, so you should begin migrating extensions as soon as possible.

Note that there are a few exceptions to this rule, see below.

For a limited period of time, you may override this behavior. This requires you to disable the Extensibility Layer Safe Mode option in your site’s configuration record. For more information, see our documentation on the Extensions Subtab.

Contact via JavaScript or SuiteScript to a site’s source code in an extension must only be through the extensibility API. For the avoidance of doubt, things developers should not do in extensions include:

  • Add core modules as dependencies (whether in a module’s define() or dynamically via require())
  • Instance core modules
  • Extend core modules
  • Wrap the methods of core modules
  • Modify the prototypes of core modules

If you are writing a customization for SCA, remember that you can still access the extensibility API in the site’s source code, and we would encourage you to use it as much as possible.

Why Has This Changed?

There are numerous things that have factored into this decision:

  • The freedom that NetSuite developers need to make changes to the core code modules
  • The operational difficulty of migrating SuiteCommerce sites that have dependencies to core code modules in them
  • The growing maturity of the extensibility API

You could summarize this as a push for greater stability and anti-fragility.

As mentioned in our release notes over multiple releases, we are migrating our core JavaScript to TypeScript, and our SuiteScript 1.0 to 2.0. This refactoring process often necessitates changing the behavior of some modules, which can cause issues for extensions that depend on them.

Furthermore, as we make enhancements and add new features, issues around changing core modules become more pronounced on SaaS SuiteCommerce sites that are updated automatically. PaaS SuiteCommerce Advanced sites are shielded from potential problems as their manual upgrade process gives developers time to refactor their customizations.

Extensibility API Classes as Replacements

In many cases, the modules, classes and methods found in the extensibility API can be used as direct replacements for ones in the core code (with accommodations, of course). In other cases, the extensibility API introduces ways of doing things that were complicated with only the core code.

Regardless, when thinking about how to implement your customization, you can use the table below to get a sense of the areas of the core code that have coverage in the extensibility API.

Core Code ClassExtensibility API ReplacementVersion Availability
ApplicationThe container object passed to mountToApp2018.1
Backbone.CollectionSCCollection2020.1
Backbone.CollectionViewSCCollectionView2020.2
Backbone.FormViewSCFormView2020.2
Backbone.ModelSCModel2020.1
Backbone.RouterPageType component2019.1
Backbone.ViewSCView2020.2
Cart.Detailed.ViewCart component2018.1
Facets.Browse.ViewPLP component2018.1
Header.MiniCart.ViewCart component2018.1
LiveOrder.ModelCart component2018.1
LoginRegister.ViewLoginRegisterPage component2019.1
MenuTrue.ViewMyAccountMenu component2019.1
ProductDetails.Full.ViewPDP component2018.1
ProductDetails.Quick.ViewPDP component2018.1
Profile.ModelUserProfile component2019.1
SC.ConfigurationEnvironment component2018.1 R2
SC.ENVIRONMENTEnvironment component2018.1 R2
SessionEnvironment component2018.1 R2

Allowed Core Modules

There are a number of modules that are found in the core code that are permitted to be included in extensions.

Module NameIntended UseNotes
Backbone.ValidationTo be extended to define a custom validation schema for a custom model class 
CustomContentType.Base.ViewTo be extended when defining a new custom content type (CCT) view classSee Develop a Custom Content Type as an Extension
HandlebarsTo register new custom helper functionsSee Add and Use Custom Handlebars Helpers
jQueryDOM manipulation, AJAX, events, deferred objects, etcDevelopers should use this library responsibly
underscoreUtility functionsNote that SuiteCommerce-specific utility functions are no longer mixed into this library (use Utils instead)
UtilsSuiteCommerce-specific utility functions 
Wizard.ModuleTo be extended when defining a custom checkout viewSee Add a New Module to the Checkout with the Extensibility API

Using Core Modules in Extensibility API Methods

Generally speaking, if the extensibility API method you are using is set up to reference specific core modules, then you can use them as they are intended. For example, referencing a core module in the addToViewContextDefinition() method is fine, eg:

// ✅ All good
var Layout = container.getComponent('Layout');
Layout.addToViewContextDefinition('Header.View', 'MyNewMessage', 'string', function (context) {
  return 'Hello World!'
});

In this scenario, we are using an extensibility API method as designed, referencing a core module without creating a dependency to it. Should this view ever stop existing, or otherwise fail to be called, the API call will fail gracefully and there is no risk to the site breaking.

But note that some methods do not permit this. For example, addChildViews() allows for a more ‘targeted’ approach to adding child views using what we call ‘verbose syntax’. This verbose syntax style is useful for being more specific in how you add your child view but letting you go a bit ‘deeper’ in the view chain.

However, using this method, the parent/layout view you are targeting can only be core code view if it is one that the component you are using ‘owns’. These views have capitalized property names on the component. Thus, for example, the following example is OK:

// ✔️ All good
PLP.addChildViews(PLP.PLP_VIEW, {
    'ItemViews.Price': {
        'MyNew.View': {/*...*/}
    } 
})

However, this example is not:

// ❌ Don't do this
Layout.addChildViews('Facets.Browse.View', {
    'ItemViews.Price': {
        'MyNew.View': {/*...*/}
    } 
})

This is because the layout component does not ‘own’ this parent view. Using the code in this second example will result in the following console warning:

INVALID_PARAM Invoking the method "addChildViews" with an incorrect parameter: (Facets.Browse.View).
It will still work but it will be deprecated in the future. Please check the documentation.

In many cases, you can still add your new child view to an existing core code view, but you will need to use the ‘simple’ syntax above.

(Also) Avoid Using Backend Core Modules

The advice for SuiteScript mirrors that for JavaScript: you should not include core SuiteCommerce modules in your extensions’ SuiteScript. This includes popular modules such as LiveOrder.Model, SCModel and SCModels.Init. Furthermore, as of 2020.1, the backend cart component in the extensibility API is deprecated.

As extensions now support SuiteScript 2.0, all developers are now encouraged to develop their extensions to use the core SuiteScript 2.0 API only. Note that no SuiteScript 2.0 modules in the SuiteCommerce source code are currently exposed to extensions or the extensibility API, but there are some commerce-specific modules in the SuiteScript 2.0 API.

Examples of Extensibility API Best Practice

All of this is theoretical, so let's look at some examples. This is particularly important if you are an experienced SCA developer with historical knowledge — many of the things you might have done with a core implementation are not appropriate for extensions.

Getting Configuration Record Values

In the past you may have used the SC.Configuration module or even the SC global variable — we strongly discourage this. Now we have the environment component, so just use that and then use its getConfig() method.

// ❌ Don't use the SC global variable
var isMultiShipEnabled = SC.CONFIGURATION.isMultiShippingEnabled;

// ❌ Don't add the Configuration module as a dependency
var isMultiShipEnabled = Configuration.isMultiShippingEnabled;

// ✅ Use the Environment component of the extensibility API
var Environment = container.getComponent('Environment');
var isMultiShipEnabled = Environment.getConfig('isMultiShipEnabled');

Adding a New Child View

If you've written some new functionality that plugs into existing functionality, then you'll likely need to add a new child view. Ever since we made every view a composite view in the Elbrus release, adding a new view meant adding the view you want to add a child to as a dependency, and then just modifying the childViews object of its prototype. Prior to that, there was a lengthy process which involved converting simple views into composite views but, now, with the extensibility API, you can use a component specific for an area of a site, or use the layout component in general.

// ❌ Don't modify a core module's prototype object
CartSummaryView.prototype.childViews.MySuperCoolView = function () {
  return new MySuperCoolView ({
    model: this.model
  })
}

// ✅ Use a visual component's addChildView() method
var Cart = container.getComponent('Cart');
Cart.addChildView('Cart.Summary', function () {
  return new MySuperCoolView ({
    model: this.model
  })
});

Another way of making child views available is through something we added in 2018.2: registerView(). Unlike addChildView, which requires you to specify at the time of construction where you want it to go, this method lets you create a new child view that can be included anywhere on a site by simply adding markup to templates.

// ✅ If your page doesn't have a specific visual component, then you can use the the Layout component
var Layout = container.getComponent('Layout');
Layout.registerView('MySuperCoolView', function () {
  return new MySuperCoolView
});

This method is perfect for situations where your extension adds new functionality but it is up to the implementor where it should be rendered. It's also good when responsibilities between theme development and code development are divided: ie, new views can be added without updating the JavaScript core code / extension after the initial implementation.

Adding a New Value to a Context Object

The Underscore method wrap() is useful is core code customizations because it enables us to run a method’s function and modify its output before it’s returned. In other words, if we have a finished context object, but want to add an additional property to it, then we can put it in a wrapper function that runs it, and then adds in our property.

With the extensibility API, we can access the addToViewContextDefinition() method which does the job for us. We specify the view whose context we want to modify, the property name (new or existing), its type, and then the function that determines its value. Handily for us, the existing context object is passed along with it, so we can query it, should we need a value out of it.

// For example, I want to add a property that returns the number of characters in the name of an item
// ❌ Don't modify a core module's prototype or wrap its context object generator method
ProductDetailsFullView.prototype.getContext = _.wrap(ProductDetailsFullView.prototype.getContext, function(fn) {
  var context = fn.apply(this, _.toArray(arguments).slice(1));
  context.nameLength = this.model.get('item').get('displayname').length;
  return context;
});

// ✅ Use a visual component's addToViewContextDefinition() method to modify a view's context object 
var PDP = container.getComponent('PDP');
PDP.addToViewContextDefinition(PDP.PDP_FULL_VIEW, 'nameLength', 'number', function nameLength (context) {
  return context.model.item.displayname.length
});

Modify Backend Cart Behavior (SCA Only)

As noted above, the backend cart component is now deprecated but it is stable in SuiteCommerce Advanced sites running versions 2018.1-2020.2.

To modify the behavior of the cart, you should use the backend cart component.

The cart component has a number of methods, as well as a myriad of events that you can listen for. The backend component is designed to have the same syntax and behavior of the frontend one, and although it operates synchronously behind the scenes, you can still write code that is asynchronous (ie, it won't operate asynchronously but it means the code you use on the frontend can be easily ported to the backend).

If available and stable, it is preferable to use the backend cart component rather than modify the core LiveOrder class.

So, let's take another example, for which there are (at least) two ways of doing it. Let's say that when a shopper updates a line item in the cart (eg adjusts its quantity) we want to trigger a check so that we can test if its purple.

// ❌ Don't extend the core module or wrap its methods
_.extend(LiveOrderLineServiceController, {
  put: _.wrap(LiveOrderLineServiceController.put, function (fn) {
    var options = this.data.options;

    _.each(options, function (option) {
      if (option.value && option.value.label == 'purple') {
        console.log('I like purple!')
      }
    });

    return fn.apply(this, _.toArray(arguments).slice(1));
  })
});

// ⚠️ Avoid using the Application module's backend events
Application.on('after:LiveOrder.Line.ServiceController.put', function (line) {
  // ...
});

// ✅ Use the backend Cart component's cancelable events
var Cart = Application.getComponent('Cart');
Cart.on('afterUpdateLine', function checkForPurple (data) {
  var options = data.line.options;

  _.each(options, function (option) {
    if (option.value && option.value.label == 'purple') {
      console.log('I like purple!')
    }
  });
})

In other words, the backend cart component works like a wrapper for the LiveOrder model. So, I can recommend the above approach because it is possible to achieve what I want to achieve without touching the base class by virtue of the cart component.

Frequently Asked Questions

Are API Components and Methods Forwards Compatible?

Generally, yes — extensibility API classes, methods and events should continue to operate as expected in newer versions.

Deprecations in the extensibility API are rare and we take steps to ensure that extensibility API existing methods and events do not break or change behavior in newer releases. If we do make changes, we will communicate them through the release notes and, if necessary, PFCNs.

Are API Methods Backwards Compatible?

No — if you reference a method used in a newer version of the API than the site has access to, it won't work. Components that are unavailable in your current version will return null when called.

We recommend adding fail-safe checks before using a component or method (such as checking they exist) before using them in your extensions.

// Get your desired component
var UserProfile = container.getComponent('UserProfile')

// Check if it exists before trying to use it
if (UserProfile) {
    // Then call it and its methods...
}

// Will fail gracefully if not available

If the Extensibility API Isn’t Backwards Compatible, How Can I Tell If My Extension Will Work On a Particular Site?

SuiteCommerce sites are always either on the latest version or scheduled to be updated to the latest version. Updates are phased, and so you should make use of the ability to specify version compatibility in your extension’s metadata to make sure that when a user goes to update their extension, they do not install a version that is not compatible with their site.

// For example, in your manifest.json
"target_version": {
    "SCA": ">=20.2.0",
    "SCS": ">=20.2.0"
},

When a user goes to activate, sites that are incompatible with the target_version in an extension’s manifest are hidden.

As for SuiteCommerce Advanced, the Extensibility API documentation includes notes for many components and methods on when they were introduced.

Finally, you can also check the Architecture and Extensibility API rows in the SuiteCommerce Advanced Feature Compatibility Matrix, which indicates which versions certain architectural aspects are compatible.

Can I Extend Extensibility API Components?

It is technically possible but please don’t.

Can I Extend Extensions?

It is technically possible but not recommended unless you own/control the extension you are extending.

I think when you're considering this, you need to be careful. By doing this you're effectively creating a dependency in an environment where it's possible for a site administrator to disable one using the backend interface. Generally speaking, if these are extensions that you own and operate, it's probably low risk if the site administrators are aware that they are not to be treated independently.

Treat third-party (or NetSuite) extensions like you would core modules: the original author may push an update for their extension that changes the behavior, removes a class or method, and you're stuck having to either rewrite your extension or removing it entirely.

In this sense, you might think of extending extension classes as like extending base SuiteCommerce classes — we generally don't recommend it, and the risks detailed above (about moving sites or versions) apply.

If you are going to extend an extension consider the following:

  • If you own the extension, consider creating an API for it within that extension's code
  • If you don't own the extension, consider creating an adapter for it and then have your customization talk to it through that

There is a very good diagram (and explanation) in our article on The Extension Framework and Extensibility API.

Can I Use Overrides?

Extensions do not offer any mechanism for overriding files.

The only overrides possible as part of the extension framework is the ability in your theme to override any templates that an extension adds. This is useful if you install a third-party extension but don’t like how it fits in with the rest of your site’s look-and-feel.

See Override an Extension’s Template and Sass Files.

Final Thoughts

When it comes to customizing SuiteCommerce Advanced, the rules have changed. And they have been brought in line with the advise we give to SuiteCommerce site developers: upgradeability, reusability and maintainability. We also want to recommend that whatever customizations you make, you evaluate the risk of how it will affect your site when you upgrade and if you were to migrate it to another site.

Keeping a site fresh, using the latest source should be the prize you keep your eye on. However, we're aware that if you're using SuiteCommerce Advanced over SuiteCommerce, there is a good chance that you want to get into the guts of the application — and that's OK — but think about the changes you're making and whether they are things that could be made through extensions or, at least, made in such a way that does not hinder your ability to upgrade. We still encourage you to use the extensibility API and extensions as much as possible. Remember, the API is available throughout the source application, but you will need to have access to the container/application object in order to invoke its components and their methods.