Show/Hide Item Fields by Category in NetSuite
Skip the per-category Entry Form. A client script on pageInit and fieldChanged hides the spec fields that do not belong to the chosen category.

On this page
You have a custom field called Item Category with a drop-down list, and you need chain items to show chain-specific spec fields while spoke items show spoke-related fields. The native solution isn't a per-category Entry Form. It's a client-side script that runs on page load and reacts when the category changes.
The Problem With Entry Forms
Using separate Entry Forms per category works, but it creates a maintenance burden. Every new category means a new form, and every field addition means editing multiple forms. Users also have to remember to select the correct form, or you need SuiteScript to swap it automatically.
The cleaner approach uses a Client Script with a pageInit entry point that hides fields based on the current category, plus a fieldChanged entry point that triggers when the user changes the category on the item record. Client scripts only run in edit mode, so this logic fires when the user clicks Edit on an existing item or creates a new one.
How the Script Works
The script reads the Item Category custom field value when the item record loads. It then iterates through a mapping of category-to-field arrays and calls setDisplay({ fieldId: 'custitem_chain_links', isDisplay: true }) on the relevant fields.
Here's the key detail: setDisplay controls visibility at the field level. It works on both body fields and sublist fields. For this use case, you're working with body fields on the item record. The N/currentRecord module provides access to these fields without a separate load call, since the entry point injects the current record directly.
The Client Script
/**
* @NApiVersion 2.1
* @NScriptType ClientScript
*/
define(['N/record', 'N/ui/currentRecord'], (record, currentRecord) => {
// Map each category value to its visible field IDs
const CATEGORY_FIELD_MAP = {
'CHAIN': ['custitem_chain_links', 'custitem_chain_speed'],
'SPOKE': ['custitem_spoke_length', 'custitem_spoke_thickness'],
'RIM': ['custitem_rim_width', 'custitem_rim_diameter'],
'HUB': ['custitem_hub_bearing_count', 'custitem_hub_axle_type']
};
// All field IDs that participate in show/hide logic
const ALL_FIELDS = Object.values(CATEGORY_FIELD_MAP).flat();
const hideAllFields = (currentRecord) => {
ALL_FIELDS.forEach((fieldId) => {
currentRecord.setDisplay({
fieldId: fieldId,
isDisplay: false
});
});
};
const showFieldsForCategory = (currentRecord, category) => {
const fieldsToShow = CATEGORY_FIELD_MAP[category] || [];
fieldsToShow.forEach((fieldId) => {
currentRecord.setDisplay({
fieldId: fieldId,
isDisplay: true
});
});
};
const updateFieldVisibility = (currentRecord) => {
const category = currentRecord.getValue({
fieldId: 'custitem_item_category'
});
hideAllFields(currentRecord);
showFieldsForCategory(currentRecord, category);
};
const pageInit = (scriptContext) => {
const currentRecord = scriptContext.currentRecord;
updateFieldVisibility(currentRecord);
};
const fieldChanged = (scriptContext) => {
const currentRecord = scriptContext.currentRecord;
if (scriptContext.fieldId === 'custitem_item_category') {
updateFieldVisibility(currentRecord);
}
};
return {
pageInit: pageInit,
fieldChanged: fieldChanged
};
});Deployment and Permissions
Deploy this script to the Item record type with a Client deployment. Go to Customization > Scripting > Scripts > New, create a Client Script, paste the code above, then use the Deployments tab to create a new deployment targeting the Item record. Set the status to Released.
In the deployment's Audience tab, apply it to the roles that create and edit items. The script runs in the browser, so it won't consume governance units. Note that the script deployment record's internal ID is scriptdeployment if you need to reference it elsewhere.
The Gotcha: Field IDs and Internal Values
The custom field IDs (custitem_chain_links, custitem_spoke_length, etc.) must match exactly what's defined on the item record. You can find them under Customization > Lists, Records, & Fields > Item Fields. The field ID is listed on the edit page of each custom field.
The category values in the map must be the internal IDs of the drop-down options, not the display labels. If your category list shows "Chain" but the internal ID is CHAIN, the script won't match. To find internal IDs, open the custom field definition and look at the Custom Segment or Custom List values. The internal ID appears in the URL when you click each option.
Why Not a Workflow?
You can achieve the same result with a workflow using the Set Field Value action with the "Show or Hide Field" option. But workflows add record-level overhead, and the logic becomes harder to maintain as your category list grows. A client script keeps everything in one place and is easier to version-control.
Reporting Considerations
This approach keeps each spec as its own field, which means saved searches can filter and report on custitem_chain_links directly. That's the advantage over a parameter1/parameter2 approach, where you'd need to read the label field to know what data the value field holds.
The tradeoff is field count. If you have 20 categories with 5 fields each, that's 100 custom fields on the item record. NetSuite allows this, but it makes the Custom Fields list unwieldy. Group fields with a naming convention like custitem_chain_links and custitem_spoke_length so admins can filter and find them.
The Edge Case: New Items
When someone creates a new item, the category field is blank until they select it. Your pageInit will hide all spec fields initially. The fieldChanged handler fires when the user picks a category, and the relevant fields appear. This works correctly, but you should set the category field as mandatory on the item form so users can't save an item without a category.
Go to Customization > Forms > Entry Forms > Item and set the Item Category field to mandatory on the preferred form. The fieldChanged entry point receives the field ID that changed in scriptContext.fieldId, which is how the script knows to re-evaluate visibility.
What This Won't Do
This script controls visibility only. It does not prevent someone from saving a value in a hidden field via CSV import or SuiteScript. If you need data integrity at the record level, add a User Event Script with a beforeSubmit entry point that validates the category matches the populated fields.
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/record', 'N/log'], (record, log) => {
const beforeSubmit = (scriptContext) => {
if (scriptContext.type !== scriptContext.UserEventType.CREATE &&
scriptContext.type !== scriptContext.UserEventType.EDIT) {
return;
}
const rec = scriptContext.newRecord;
const category = rec.getValue({ fieldId: 'custitem_item_category' });
const categoryFields = {
'CHAIN': ['custitem_chain_links', 'custitem_chain_speed'],
'SPOKE': ['custitem_spoke_length', 'custitem_spoke_thickness']
};
const allowedFields = categoryFields[category] || [];
// Check if any non-allowed field has a value
const allFields = Object.values(categoryFields).flat();
allFields.forEach((fieldId) => {
if (!allowedFields.includes(fieldId) && rec.getValue({ fieldId: fieldId })) {
throw new Error(`Field ${fieldId} is not valid for category ${category}`);
}
});
};
return { beforeSubmit };
});This user event script runs on the server during save operations, so it catches imports and API writes that bypass the client script. Deploy it to the Item record with a User Event deployment.
The combination of a client script for UI behavior and a user event for server-side validation covers both paths. Test both in your sandbox with a chain item and a spoke item before rolling out to production. If you're managing many custom fields across categories, audit field usage periodically and clean up unused fields that clutter the item record over time.


