Records are defined by their fields, and while there are a number of standard fields on our standard records, it is common for people to add their own custom ones. This article describes how you can expose this data to the frontend of your web store.

From a theoretical point of view, it is possible to expose custom data from almost every record your web store depends upon but because of how the backend works and how data is handled in the bundles, there are limitations and differences that are difficult to explain and document.

However, the good news is that the most commonly used records are relatively straightforward — this ease has improved over the various releases, so much so that sites running SuiteCommerce or modern versions of SuiteCommerce Advanced now have automatic access to some custom fields.

When we think of the most commonly used custom record types, we are specifically thinking about:

  1. Item Fields — read-only custom fields on item records, for when you want to show additional information on products (eg, to describe the warmth of the gloves in your inventory)
  2. Transaction Item Options — custom fields on item records that can be used in conjunction with custom item fields, for when you want to offer matrix child items or other item-specific customizations (eg, color, size, monogramming)
  3. Transaction Body Fields — custom fields on transaction records, which apply to an order as a whole (eg, for the shopper to specify a preferred delivery date)
  4. Transaction Line Fields — custom fields on transaction records, which adds a new field to every line on the order (eg, line-specific packing request)
  5. Entity Fields — custom fields on the customer and contact records (eg, compliance information, such as whether they have agreed to your terms and conditions)

The good news is that there is standard implementation methods for all of these, which is covered either by:

  • An extension built by NetSuite, for sites running SuiteCommerce, or SuiteCommerce Advanced Aconcagua or newer
  • Comprehensive documentation

So, when it comes to documentation, not only is there a plethora of information there about how to set up these fields in the backend, there is also information on how to implement them on the frontend:

As for the extension, you can find the SuiteCommerce Custom Fields extension built by NetSuite on SuiteApp.com and in SuiteBundler. After installation and activation, this extension makes it super easy for business users to add in custom fields to PDPs and the checkout. It is strongly recommended that, if you can run extensions, you evaluate whether this is a good fit for you before trying to write custom code.

Ergo, this article will not go in a lot of detail regarding the first four listed above. We will discuss entity fields, and I will include some additional general information on item fields.

Example: Read-Only Custom Item Fields

When you add a custom field to an item, the idea is that you are offering additional information about a product. A key benefit of doing it in a custom field over, say, just adding more information the description, is that it becomes a searchable field, which means that it be used as a facet for refinement.

They can also be paired with transaction item option fields, which enables you to use them as matrix options and create child items. This section will focus on creating just the custom item field.

The general approach is going to be:

  1. Create the custom field type in NetSuite
  2. Update an item record so that there is at least one item with a value for that field
  3. Update a field set to include that field (eg the details field set, if you want to surface it on the PDP)
  4. Run a search index
  5. Verify that the items search API response for that item returns data for that item’s custom field
  6. Get the data to appear in the right place on the site, which usually means editing a JavaScript and/or template file

Set Up the Custom Item Field in NetSuite

The process for doing this is described in our documentation.

Do these steps before proceeding.

Verify the Data

You can verify that your item has been updated and that the data is exposed via the items API by performing a query on the items API. For example:

// Pattern
jQuery.ajax('/api/items?id=<item ID>&fields=<custom item field ID>').then(
    function (items) {console.log(items.items[0])}
)

// Example
jQuery.ajax('/api/items?id=6673&fields=custitem32').then(
    function (items) {console.log(items.items[0])}
) 

In the above code, you will need to substitute in the item ID for the item and the custom record ID. You may also need extra parameters to the URL indicating your company ID, price level, locale, language, etc.

If the call succeeds, you will see the item data object which just has the internal ID and custom field you specified.

You can further verify that is exposed to your desired field set by substituting the fields parameter for the fieldset parameter, and then including the name of the field set you’re going to use. For example, you may want to test fieldset=details. If this also succeeds, then you can be reasonably sure that the data will also be available to the view that will need access to it.

Add the Data to Your Site

To make the data appear in your site, you will need to locate the appropriate template in your theme or source code. If you’re unsure, you can use your browser developer tools to inspect the exact spot and then find the nearest comment in the code that says TEMPLATE STARTS.

When you’re in the template source file, you can access the item model by using:

<!-- Pattern -->
{{model.item.<custom item field ID>}}

<!-- Example -->
{{model.item.custitem32}}

If this works, then you’re on your way.

Before adding this code raw to your site, you should add a check in your template to see if it exists first; this can prevent unnecessary code from executing. For example:

<!-- Check if it has a value -->
{{#if model.item.custitem32}}
    <div class="product-details-full-warmth">{{model.item.custitem32}}</div>
{{/if}}

<!-- You can also check whether it has a specific value, which is important for booleans -->
{{#ifEquals model.item.custitem32 'T'}}
    <div class="product-details-full-warmth">{{model.item.custitem32}}</div>
{{/ifEquals}}

Note that ifEquals is not available to versions older than Elbrus, so if you need this functionality, you will need to create a custom Handlebars helper to implement this.

Don’t forget to add it to other templates where the item data is exposed, such as the cart and checkout.

Custom Entity Fields

These are fields that you tie to a user record, such as a customer record.

The general approach is:

  1. Create the custom field type in NetSuite
  2. Update a customer record so that there is at least one customer with a value for that field (eg a test user)
  3. Include the custom field (or all custom fields) in the request (this is done automatically in SuiteCommerce, and SCA 2019.1 and newer)
  4. Access the data model for the user by either using the UserProfile component (SC, and SCA 2019.1 and newer) or the Profile.Model singleton
  5. Update the template, if necessary, to display this data

Create the Custom Entity Field and Update a Record

Go to Customization > Lists, Records & Fields > Entity Fields > New to create a new field.

Generally speaking, you will want to enable Store Value and make sure it Applies To the Customer and Web Site.

Save it.

Then, look up a test user in NetSuite and check the new custom field is there. Set a value for it.

Include the Custom Field in the Request

Skip this step if you are using SuiteCommerce, or SuiteCommerce Advanced 2019.1 or newer. Since 2019.1, all custom fields are automatically available.

With the data in NetSuite, the next question is how you get the data to the frontend. In the source code, we have a data model called Profile.Model that fetches user data from NetSuite and makes it available on the frontend. The important code is the SuiteScript code, and that is where we need to make modifications.

By default, the model is set up only to retrieve the fields that are specified in Profile/SuiteScript/Profile.Model.js. You can see the list of fields in the get() function. What you’ll notice, conspicuously, is that there are no custom fields being requested. So, we need to change that.

Luckily for us, there is a commerce API method on the customer object called getCustomFields() that will do all the work for us. The trick, therefore, is to call this method in the profile model.

Rather than edit the file directly, we will, instead, wrap Profile.Model.get() so that it includes the call.

To do this, you will need to create a custom module/extension. I would lay it out like this:

UserCustomFields

  • SuiteScript
    • UserCustomFields.js
  • ns.package.json

Old versions of SCA will need to include a version number in the module folder name, eg UserCustomFields@1.0.0.

Then, in UserCustomFields.js, put the following code:

define('UserCustomFields'
, [
    'Profile.Model'
  , 'SC.Models.Init'
  ]
, function
  (
    ProfileModel
  , ModelsInit
  )
{
  'use strict';

  ProfileModel.get = _.wrap(ProfileModel.get, function (fn)
  {
    var profile = fn.apply(this, _.toArray(arguments).slice(1));
    profile.customfields = ModelsInit.customer.getCustomFields();

    return profile;
  });
});

SC.Models.Init is a useful module that providers a wrapper for the commerce API, making it easier to work with, so we add that as a dependency.

Then we use Underscore’s wrap() method to redefine Profile.Model.get(). What this does is take the existing function, passes it to a new function, which then lets us run code before or after the original function. So, if you look inside our new function, you can see that we take the original function, call it, and assign it to the profile variable. Then, on the same object, set the customfields property to equal the customer’s custom fields by calling ModelsInit.customer.getCustomFields(). This final value is returned from the new function, seamlessly providing the original object with an additional value attached to it.

Update your ns.package.json to make sure your code is included in compilation:

{
  "gulp":
  {
    "ssp-libraries": ["SuiteScript/*.js"]
  }
}

If you’re using SCA, you will need to update your distro.json to include the SuiteScript.

Update the modules object to include a path to your new module (eg "customizations/UserCustomFields": "1.0.0"), and also update the ssp-libraries : dependencies array to include the new file ("UserCustomFields").

Save and deploy; if it’s an extension, you’ll need to activate it.

Once the code is deployed (and activated) and ready to use, you can test your work by logging in as your test user and running the following from your developer console:

require('Profile.Model').getInstance()

The customfields object should be there in the attributes object with your custom field data.

Note the getInstance() — this is because we operate the profile model as a singleton. This means that rather than allowing multiple invocations of this object, we operate one master instance of the object. This ensures that data stays in sync. When accessing the ‘live’ data of the current user, always use the singleton instance of it.

Access the User Data

If you are planning on modifying/overriding a template that already accesses it (ie for standard field data), you can probably follow that template’s convention without changes to your JavaScript.

For example, Header.Profile.View, which is the view that renders the greeting in header, already exposes the profileModel object to the template, so you can access customfields directly from it. Therefore, no JavaScript changes are required.

If you need it somewhere else, you will need to follow the steps depending on your version.

One thing that is universal is the profile data can take time to load, and so you will need to listen to its promise before attempting to use it.

For more information on promises, see Understand jQuery Promises and Deferred Objects.

Note that the following examples assume you want the data as read-only.

SuiteCommerce, and SuiteCommerce Advanced 2019.1 and Newer

For newer sites, you can access profile data using the UserProfile component in the extensibility API. To do this, you will need access to an application object, such as container in your mountToApp() method and then use getUserProfile() on it. This will return a promise — you can do a number of things with that.

  1. Pass the promise to your view, and let it deal with it (by doing one of the following)
  2. Wait for the promise to resolve and then render the view, passing it the profile data
  3. Put placeholders in and then re-render the view when the promise resolves

There’s no one best option — it depends what you want to do.

However, for the sake of an example, let’s assume you’re working on a router and you’re going to render a view once you have the profile data. You could have this:

define('Vendor.Extension.Module.Router'
, [
    'Backbone'
  , 'Vendor.Extension.Module.View'
  ]
, function
  (
    Backbone
  , VendorExtensionModuleView
  )
{
  'use strict';

  return Backbone.Router.extend({
    routes:
    {
      'my-new-path': 'myRoute'
    }

  , initialize: function (container)
    {
      this.application = container;
      this.UserProfile = container.getComponent('UserProfile');
    }

  , myRoute: function ()
    {
      var self = this;
      this.UserProfile.getUserProfile().then(function (profileData)
      {
        var view = new VendorExtensionModuleView
        ({
          application: self.application
        , profileData: profileData
        });

        view.showContent();
      });
    }
  })
});

You can see that once getUserProfile() resolves, we chain on a then() which allows us to add a callback to do something with the profile data, once we have it. And that callback then renders a view.

Now the view has access to this.profileData, and that can be sent to the template’s context.

One final note on this, it is important to note that the data returned by getUserProfile() is not an instance of a Backbone model. It is a new object based on the user profile model, so it cannot be used as if it were.

If you need the model specifically, follow the steps for older versions below.

SuiteCommerce Advanced 2018.2 and Older

For older sites, you will need to add Profile.Model as a dependency. To access the data immediately, you can use its get getInstance() method to return the data model.

However, you may need to wait for its promise to resolve, so you could do something like this:

define('Vendor.Extension.Module.AnotherView'
, [
    'Backbone'
  , 'Profile.Model'
  ]
, function 
  (
    Backbone
  , ProfileModel
  )
{
  'use strict';

  return Backbone.View.extend({
    initialize: function ()
    {
      var self = this;

      ProfileModel.getPromise().done(function ()
      {
        self.render();
      });
    }

  , getContext: function ()
    {
      return {
        profileModel: ProfileModel.getInstance();
      }
    }
  })
});

In this example, I am passing the profile model instance immediately to the view’s context, while telling it to re-render the view once the promise resolves. You could also set the view to use the profile model as its model, if you wish.