Extension to Show Category in website based on the Role and Domain

Overview:

An extension named ‘ShowCategoryByRole’ for NetSuite Commerce is designed to dynamically hide or display categories in the site header, navigation facets, and category pages based on customer roles and domain classifications. This multi‑domain solution leverages custom category fields for customer segment and domain assignment, ensuring that only relevant categories appear to each user segment.

1. Purpose

  • Limit category visibility to specific customer segments (roles) and shopping domains.
  • Improve navigation relevance and reduce clutter for end users.
  • Support multi‑domain websites with shared catalog structure.

2. Prerequisites

  • NetSuite Commerce (SuiteCommerce Advanced) environment
  • Extension deployment privileges
  • Custom category fields:
  • custrecord17: Domain assignment (list/record)
  • custrecord_jj_customer_segment_role: Segment/role assignment (list/record)

3. Architecture

3.1 Front‑end Module (Client Entry Point)

  • File: JJ.ShowCategoryByRole.ShowCategoryByRole.js
  • Dependencies:
  • Header.Menu.View
  • Facets.CategoryCell.View
  • Facets.FacetedNavigationItemCategory.View
  • Facets.Browse.View
  • Facets.FacetedNavigationItem.View
  • Backbone.CollectionView
  • Profile.Model & Header.Profile.View
  • JJ.ShowCategoryByRole.ShowCategoryByRole.Model
  • Key Extensions:
  • Wrap HeaderProfileView.getContext to inject user name.
  • Wrap HeaderMenu.initialize to fetch filtered categories and override SC.CATEGORIES.
  • Extend FacetsBrowseView and FacetsFacetedNavigationItemCategoryView to apply the same filtering logic in facets and browse pages.
  • Filtering Logic:
  1. Fetch JSON list of categories assigned to each domain/segment from backend.
  2. Traverse SC.CONFIGURATION.navigationData (header menu) and SC.CATEGORIES (facet defaults).
  3. Recursively include only those categories whose domain includes the current shoppingDomain and whose segment matches the logged‑in user segments.

3.2 Backend Service (Server Side)

  • Controller: ShowCategoryByRole.ServiceController
  • Model: JJ.ShowCategoryByRole.ShowCategoryByRole (SCModel extend)
  • Service Endpoint: GET /services/ShowCategoryByRole.Service.ss
  • Data Retrieval:
  1. Perform a NetSuite commercecategory search:
  • Filter: displayinsite is T
  • Columns: name, primaryparent, pagetitle, urlfragment, internalid, custrecord17 (domain), custrecord_jj_customer_segment_role (segment), fullurl
  1. Map each record to a simplified JSON object:
{
  name,
  internalid,
  urlfragment,
  pagetitle,
  primaryparent,
  domain: custrecord17,
  segment: custrecord_jj_customer_segment_role,
  fullurl
}
  1. Return the array to client.

4. Installation

  1. Add the extension files to your SCA project under Extensions/ShowCategoryByRole/.
  2. Update manifest.json to reference:
"modules": [
  "JJ.ShowCategoryByRole.ShowCategoryByRole"
]
  1. Deploy to the file cabinet and activate the extension through SuiteCloud Deployment.
  2. Publish changes to the website.

5. Configuration

  1. Custom Fields:
  • Navigate to Setup > Customization > Record Types > Commerce Category.
  • Create or verify fields:
  • Domain Assignment (custrecord17): List/Record type pointing to domain records.
  • Customer Segment/Role (custrecord_jj_customer_segment_role): List/Record for segment names.
  1. Assign Values:
  • Edit each category record.
  • Select appropriate domain(s) and role(s).

6. Usage

  • Anonymous Users see only categories without segment restrictions or those marked for a default public segment.
  • Logged‑in Users see categories matching their ProfileModel.get('segments').
  • Multi‑domain: Only categories whose custrecord17 includes the current SC.ENVIRONMENT.shoppingDomain appear.

7. Troubleshooting

IssueResolutionNo categories visible in header or facetsEnsure custom fields are populated and displayinsite is true.Logged‑in user sees unauthorized categoriesVerify user’s segment assignments in their profile.Changes not reflected after deployClear site cache and HTTPS Redirects if enabled.Backend service returns empty or errorCheck ServiceController logs and ensure search filters are correct.

Front end code on Entry Point file

define(
    'JJ.ShowCategoryByRole.ShowCategoryByRole', [
    'Header.Menu.View',
    'Facets.CategoryCell.View',
    'Facets.FacetedNavigationItemCategory.View',
    'Facets.Browse.View',
    'Facets.FacetedNavigationItem.View',
    'Backbone.CollectionView',
    'Profile.Model', 
    'Header.Profile.View',
    'JJ.ShowCategoryByRole.ShowCategoryByRole.Model',
],
    function (
        HeaderMenu,
        FacetsCategoryCellView,
        FacetsFacetedNavigationItemCategoryView,
        FacetsBrowseView,
        FacetsFacetedNavigationItemView,
        BackboneCollectionView,
        ProfileModel,
        HeaderProfileView,
        JJShowCategoryByRoleModel,
    ) {
        'use strict';


        return {
            mountToApp: function mountToApp(container) {


                _.extend(HeaderProfileView.prototype, {
                    getContext: _.wrap(HeaderProfileView.prototype.getContext, function (fn) {
                        var originalRet = fn.apply(this, _.toArray(arguments).slice(1));
                        var profile = ProfileModel.getInstance();
                        originalRet.displayName = profile.attributes.name;
                        return originalRet;
                    })
                });


                _.extend(HeaderMenu.prototype, {
                    initialize: _.wrap(HeaderMenu.prototype.initialize, function (fn) {
                        fn.apply(this, _.toArray(arguments).slice(1));
                        var self = this;
                        var profile = ProfileModel.ProfileModel ? ProfileModel.ProfileModel.getInstance() : ProfileModel.getInstance();
                        self.userSegment = profile.get('segments');
                        var newModel = new JJShowCategoryByRoleModel();
                        newModel.fetch().done(function (response) {
                            self.domainItems = response
                            var domainItems = response;
                            var mainCategories = SC.CONFIGURATION.navigationData || [];
                            mainCategories.forEach(function (el) {
                                if (el.categories || el['data-type'] == "commercecategory") {
                                    var newcat = self.filterCategories(el.categories, domainItems);
                                    if (newcat) {
                                        el.categories = newcat;
                                    }
                                }
                            });
                            self.setCategories = SC.CATEGORIES[0].name;
                            self.selectedCategory = mainCategories.filter(category => category.text === self.setCategories);
                            self.NeedToFilter = self.selectedCategory[0].categories;
                            SC.CATEGORIES[0].categories = SC.CATEGORIES[0].categories.filter(mainCategory =>
                                self.NeedToFilter.some(secCategory => secCategory.text === mainCategory.name)
                            );
                            self.render();
                        })
                            .fail(function (error) {
                                console.log("error", error);
                            });
                    }),
                    hasBaseDomain: function (item) {
                        var baseDomain = SC.ENVIRONMENT.shoppingDomain;
                        var self = this;
                        var domainMatch = false;
                        if (Array.isArray(item.domain)) {
                            _.each(item.domain, function (domain) {
                                if (domain.name === baseDomain) {
                                    domainMatch = true;
                                    return false;
                                }
                            });
                        } else if (item.domain && item.domain.name === baseDomain) {
                            domainMatch = true;
                        }
                        if (!domainMatch) {
                            return false;
                        }
                        var segmentMatch = item.segment?self.segmentMatch(item.segment):true;
                        return segmentMatch;
                    },
                    segmentMatch: function (itemSegments) {
                        var self = this;
                        var userSegments = self.userSegment;
                        if (Array.isArray(itemSegments)) {
                            for (var i = 0; i < itemSegments.length; i++) {
                                var itemSegmentName = itemSegments[i].name;
                                for (var j = 0; j < userSegments.length; j++) {
                                    if (userSegments[j].name = itemSegmentName) {
                                        return true;
                                    }
                                }
                            }
                        } else {
                            var itemSegmentName = itemSegments.name;
                            for (var j = 0; j < userSegments.length; j++) {
                                if (userSegments[j].name = itemSegmentName) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    },
                    filterCategories: function (categories, domainItems) {
                        var self = this;
                        var filtered = [];
                        _.each(categories, function (category) {
                            var match = _.find(domainItems, function (item) {
                                return item.name === category.text;
                            });
                            if (match && self.hasBaseDomain(match)) {
                                var subcategories = [];
                                if (_.isArray(category.categories)) {
                                    subcategories = self.filterCategories(category.categories, domainItems);
                                }
                                var newCategory = _.extend({}, category, {
                                    categories: subcategories
                                });
                                filtered.push(newCategory);
                            }
                        });
                        return filtered;
                    },
                });


                _.extend(FacetsBrowseView.prototype, {
                    initialize: _.wrap(FacetsBrowseView.prototype.initialize, function (fn) {
                        fn.apply(this, _.toArray(arguments).slice(1));
                        var self = this;
                        var profile = ProfileModel.ProfileModel ? ProfileModel.ProfileModel.getInstance() : ProfileModel.getInstance();
                        self.userSegment = profile.get('segments');
                        var newModel = new JJShowCategoryByRoleModel();
                        newModel.fetch().done(function (response) {
                            console.log("response", response);
                            self.domainItems = response;                
                        }).fail(function (error) {
                                console.log("error", error);
                            });
                    }),


                    hasBaseDomain: function (item) {
                        var baseDomain = SC.ENVIRONMENT.shoppingDomain;
                        var self = this;
                        var domainMatch = false;
                        if (Array.isArray(item.domain)) {
                            _.each(item.domain, function (domain) {
                                if (domain.name === baseDomain) {
                                    domainMatch = true;
                                }
                            });
                        } else if (item.domain && item.domain.name === baseDomain) {
                            domainMatch = true;
                        }
                        if (!domainMatch) {
                            return false;
                        }
                        var segmentMatch = item.segment?self.segmentMatch(item.segment):true;
                        return segmentMatch;
                    },
                    segmentMatch: function (itemSegments) {
                        var self = this;
                        var userSegments = self.userSegment;
                        if (Array.isArray(itemSegments)) {
                            for (var i = 0; i < itemSegments.length; i++) {
                                var itemSegmentName = itemSegments[i].name;
                                for (var j = 0; j < userSegments.length; j++) {
                                    if (userSegments[j].name == itemSegmentName) {
                                        return true;
                                    }
                                }
                            }
                        } else {
                            var itemSegmentName = itemSegments.name;
                            for (var j = 0; j < userSegments.length; j++) {
                                if (userSegments[j].name = itemSegmentName) {
                                    return true;
                                } else {
                                    return false;
                                }
                            }
                        }
                        return false;
                    },
                    filterCategories: function (categories, domainItems) {
                        var self = this;
                        var filtered = [];
                        console.log("categories", categories);
                        console.log("domainItems", domainItems);
                        if (domainItems)
                            _.each(categories, function (category) {
                                var match = _.find(domainItems, function (item) {
                                    return item.name === category.name;
                                });
                                if (match && self.hasBaseDomain(match)) {
                                    // Recursively filter subcategories
                                    var subcategories = [];
                                    if (_.isArray(category.categories)) {
                                        subcategories = self.filterCategories(category.categories, domainItems);
                                    }
                                    // Add filtered category to result
                                    var newCategory = _.extend({}, category, {
                                        categories: subcategories
                                    });
                                    filtered.push(newCategory);
                                }
                            });
                            console.log("filtered", filtered);
                        self.model.get('category').set('categories', filtered);
                        return filtered;
                    },
                    childViews: _.extend(FacetsBrowseView.prototype.childViews, {
                        'Facets.CategoryCells': function () {
                            var self = this;
                            var mainCategories = this.model.get('category')
                                ? this.model.get('category').get('categories')
                                : [];
                            var domainItems = self.domainItems;
                            if ((SC.CATEGORIES[0].categories).length === mainCategories.length) {
                                mainCategories = this.model.get('category')
                                    ? this.model.get('category').get('categories')
                                    : [];
                                mainCategories = self.filterCategories(mainCategories, domainItems);
                            } else {
                                mainCategories = mainCategories.filter(mainCategory =>
                                    SC.CATEGORIES[0].categories.some(secCategory => secCategory.name === mainCategory.name)
                                );
                            }
                            console.log("mainCategories", mainCategories);
                            self.model.get('category').set('categories', mainCategories);
                            return new BackboneCollectionView({
                                childView: FacetsCategoryCellView,
                                collection: mainCategories
                            });
                        },
                        'Facets.CategorySidebar': function () {
                            var self = this;
                            var mainCategories = this.model.get('category')
                                ? this.model.get('category').get('categories')
                                : [];
                            mainCategories = _.isEmpty(mainCategories) ? SC.CATEGORIES[0].categories : mainCategories;
                            var domainItems = self.domainItems;
                            var filteredCategories = self.filterCategories(mainCategories, domainItems);
                            self.model.attributes.category.attributes.categories = filteredCategories;
                            return new FacetsFacetedNavigationItemCategoryView({
                                model: this.model.get('category'),
                                categoryUrl: this.translator.getCategoryUrl()
                            });
                        },
                    }),


                });
                _.extend(FacetsFacetedNavigationItemCategoryView.prototype, {
                    initialize: _.wrap(FacetsFacetedNavigationItemCategoryView.prototype.initialize, function (fn) {
                        fn.apply(this, _.toArray(arguments).slice(1));
                        var self = this;
                        this.categories = this.model.get('categories');     
                        var navCategory = SC.CONFIGURATION.navigationData;  
                        var selectedCategoryLength = navCategory.filter(category => category.text === SC.CATEGORIES[0].name)[0].categories.length;
                        if(this.categories.length === 0){
                            if(selectedCategoryLength != SC.CATEGORIES[0].categories.length){
                                this.categories = SC.CATEGORIES[0].categories;
                            }
                        }   
                        this.model.set('siblings', this.model.attributes.categories);
                        self.render();
                    })
                });
            }
        };
    });


BackEnd Files

define(“ShowCategoryByRole.ServiceController”, [“ServiceController”, ‘JJ.ShowCategoryByRole.ShowCategoryByRole’], function(

  ServiceController, categoryrole
) {
  "use strict";


  return ServiceController.extend({
    name: "ShowCategoryByRole.ServiceController",


    // The values in this object are the validation needed for the current service.
    options: {
      common: {}
    },


    get: function get() {
      try {
      
        var resultnew = categoryrole.getCategory();
        return resultnew;
      } catch (e) {
        console.error(e);
      }
    },
  });
});

// JJ.ShowCategoryByRole.ShowCategoryByRole.js
// Load all your starter dependencies in backend for your extension here
// ----------------


define('JJ.ShowCategoryByRole.ShowCategoryByRole'
,   [
        'ShowCategoryByRole.ServiceController', 'SC.Model',
        'SC.Models.Init',
        'underscore'
    ]
,   function (
        ShowCategoryByRoleServiceController, SCModel,
        ModelsInit, _
    )
{
    'use strict';
    return SCModel.extend({
        getCategory: function (data) {
            try {
                var productFeatures=[];
                var searchFilters=[
                    ["displayinsite","is","T"]
                ];
                var searchColumns=[
                    new nlobjSearchColumn("name"),
                    new nlobjSearchColumn("primaryparent"),
                    new nlobjSearchColumn("pagetitle"),
                    new nlobjSearchColumn("urlfragment"),
                    new nlobjSearchColumn("internalid"),
                    new nlobjSearchColumn("custrecord17"),
                    new nlobjSearchColumn("custrecord_jj_customer_segment_role"),
                    new nlobjSearchColumn("fullurl")
                ]
                var commercecategorySearch=Application.getAllSearchResults('commercecategory', searchFilters, searchColumns) || {};
                var result=JSON.stringify(commercecategorySearch);
                result=JSON.parse(result);
                if (result.length > 0) {
                    _.each(result, function (eachResult) {
                        var featuredata={};
                        featuredata.primaryparent=(eachResult.columns && eachResult.columns.primaryparent) || '';
                        featuredata.name=(eachResult.columns && eachResult.columns.name) || '';
                        featuredata.urlfragment=(eachResult.columns && eachResult.columns.urlfragment) || '';
                        featuredata.pagetitle=(eachResult.columns && eachResult.columns.pagetitle) || '';
                        featuredata.internalid=(eachResult.columns && eachResult.columns.internalid) || '';
                        featuredata.domain=(eachResult.columns && eachResult.columns.custrecord17) || '';
                        featuredata.segment=(eachResult.columns && eachResult.columns.custrecord_jj_customer_segment_role) || '';
                        featuredata.fullurl=(eachResult.columns && eachResult.columns.fullurl) || '';
                        productFeatures.push(featuredata);
                    });
                }
                return productFeatures;
            } catch (e) {
                console.error('error at model', e);
            }
        },
    })
});


8. FAQs

Q: How do I add a new segment?

A: Create a new List/Record entry for segments under Lists > Relationships > Segments and assign it to categories.

Q: Can a category belong to multiple domains or segments?

A: Yes. Both custrecord17 and custrecord_jj_customer_segment_role support multi‑select.

Leave a comment

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