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.getContextto inject user name. - Wrap
HeaderMenu.initializeto fetch filtered categories and override SC.CATEGORIES. - Extend
FacetsBrowseViewandFacetsFacetedNavigationItemCategoryViewto apply the same filtering logic in facets and browse pages. - Filtering Logic:
- Fetch JSON list of categories assigned to each domain/segment from backend.
- Traverse SC.CONFIGURATION.navigationData (header menu) and SC.CATEGORIES (facet defaults).
- Recursively include only those categories whose
domainincludes the current shoppingDomain and whosesegmentmatches 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:
- Perform a NetSuite
commercecategorysearch:
- Filter:
displayinsite is T - Columns: name, primaryparent, pagetitle, urlfragment, internalid, custrecord17 (domain), custrecord_jj_customer_segment_role (segment), fullurl
- Map each record to a simplified JSON object:
{
name,
internalid,
urlfragment,
pagetitle,
primaryparent,
domain: custrecord17,
segment: custrecord_jj_customer_segment_role,
fullurl
}
- Return the array to client.
4. Installation
- Add the extension files to your SCA project under
Extensions/ShowCategoryByRole/. - Update
manifest.jsonto reference:
"modules": [ "JJ.ShowCategoryByRole.ShowCategoryByRole" ]
- Deploy to the file cabinet and activate the extension through SuiteCloud Deployment.
- Publish changes to the website.
5. Configuration
- 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.
- 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
custrecord17includes the currentSC.ENVIRONMENT.shoppingDomainappear.
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.