When a SuiteScript automation needs item data, loading the item record by its internal ID is one of the most direct approaches. However, NetSuite requires more than an ID alone. The script must provide the correct record type, use the internal ID rather than the displayed item name or SKU, and account for standard versus dynamic record behavior.
To load an item record by ID in NetSuite, use `record.load()` with the item’s record type and internal ID. In SuiteScript 2.x, the standard pattern is `record.load({ type: record.Type.INVENTORY_ITEM, id: itemId, isDynamic: false })`. The `type` value must match the actual item subtype, such as inventory item, non-inventory item, service item, or kit/package, and `id` must be the NetSuite internal ID. Once loaded, read fields with `getValue()` or `getText()`, and use sublist methods when you need line-level data.
This narrow implementation detail matters because an item’s visible identifier, often called the Item Name/Number or SKU, is not necessarily the internal ID used by SuiteScript. A valid-looking value can therefore produce a record-not-found error or load the wrong type when the script assumes every item is an inventory item.
What NetSuite record.load actually does
`record.load()` retrieves an existing NetSuite record and returns a `record.Record` object that SuiteScript can inspect or modify. Loading a record does not automatically save changes. If a script changes field values, it must call `save()` separately for those changes to persist.
For item automation, the returned record gives the script access to body fields such as:
Item Name/Number
Display Name
Description
Subsidiary
Base Price
Stock units
Purchase and sales descriptions
Inventory and purchasing settings
The available fields depend on the item subtype and account configuration. A serialized inventory item, for example, has different operational requirements from a non-inventory item. Custom fields, multiple units of measure, matrix items, bins, lots, and locations also affect what the script can read.
A record loaded through SuiteScript is not the same thing as a row returned by SuiteQL. SuiteQL is designed for querying data sources, while `record.load()` returns a record object with record APIs and sublist methods. For broader query-driven retrieval, our practical SuiteQL guide for NetSuite data explains the distinction. Use `record.load()` when your logic needs the record structure or record-level operations, not simply a result set.
Which ID should you use to load an item in NetSuite?
Use the item’s internal ID in `record.load()`, not its SKU, Item Name/Number, UPC, vendor code, or external ID.
NetSuite commonly exposes several identifiers for an item:
| Identifier | Typical purpose | Suitable for `record.load()`? |
|---|---|---|
| Internal ID | NetSuite’s unique database identifier | Yes |
| Item Name/Number | Human-readable item reference or SKU | No, not directly |
| UPC or barcode | Scanning and product identification | No |
| Vendor code | Supplier-facing reference | No |
| External ID | Integration and cross-system mapping | Not directly |
The internal ID might appear in a URL, a saved search result, a Suitelet parameter, a transaction line, or a lookup result. When an integration receives a SKU, the script must first resolve that SKU to the corresponding internal ID. Passing the SKU directly into `record.load()` creates a common failure point.
A robust integration treats identifiers explicitly. If an external system sends `ABC-100`, the script should not assume that `ABC-100` is the NetSuite internal ID. It should search for the item by the appropriate field, validate that exactly one item matches, and then load the resulting internal ID.
How to load an item record by ID with SuiteScript 2.x
The following example uses SuiteScript 2.1 and loads an inventory item in standard mode:
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/record'], (record) => {
const loadInventoryItem = (itemId) => {
const itemRecord = record.load({
type: record.Type.INVENTORY_ITEM,
id: itemId,
isDynamic: false
});
return {
internalId: itemRecord.id,
itemName: itemRecord.getValue({
fieldId: 'itemid'
}),
displayName: itemRecord.getValue({
fieldId: 'displayname'
}),
description: itemRecord.getValue({
fieldId: 'salesdescription'
})
};
};
return {
beforeLoad: (context) => {
if (context.type === context.UserEventType.DELETE) {
return;
}
const itemId = context.newRecord.id;
if (!itemId) {
return;
}
const itemData = loadInventoryItem(itemId);
log.debug({
title: 'Loaded item',
details: itemData
});
}
};
});The important parts are the `N/record` module, the record type, the internal ID, and the explicit `isDynamic` setting. The script checks for an ID before loading because a new record does not have a database ID yet. That check is especially important in `beforeLoad` and `beforeSubmit` contexts.
If the script already runs on the item record, loading the same record again is not always necessary. In a User Event script, `context.newRecord` already provides the current record context. A second load consumes governance and creates another database read. Load the record again only when the script needs a separate persisted state, a different record mode, or fields that are not reliably available from the event context.
Choosing the correct item record type
The item type is not interchangeable. NetSuite’s record API expects the type that corresponds to the actual item record. Common constants include:
| NetSuite item category | SuiteScript record constant |
|---|---|
| Inventory item | `record.Type.INVENTORY_ITEM` |
| Non-inventory item | `record.Type.NON_INVENTORY_ITEM` |
| Service item | `record.Type.SERVICE_ITEM` |
| Other charge item | `record.Type.OTHER_CHARGE` |
| Kit/package | `record.Type.KIT` |
| Assembly item | `record.Type.ASSEMBLY_ITEM` |
| Lot-numbered inventory item | `record.Type.LOT_NUMBERED_INVENTORY_ITEM` |
| Serialized inventory item | `record.Type.SERIALIZED_INVENTORY_ITEM` |
The exact constant depends on the record type supported by the SuiteScript version and the account’s enabled features. Before coding, confirm the record type in the SuiteScript Records Browser, NetSuite Records Catalog, or the item record’s metadata. Our article on using the SuiteScript Records Browser inside NetSuite covers the documentation workflow, while this article focuses specifically on loading an item by ID and handling runtime behavior.
A frequent mistake is using `record.Type.INVENTORY_ITEM` for every stock-related item. A serialized inventory item or lot-numbered inventory item can require its own type. When the type is wrong, a valid internal ID still fails because NetSuite is being asked to load the ID from the wrong record family.
Standard mode versus dynamic mode
For most read-only item lookups, standard mode is the better default:
const itemRecord = record.load({
type: record.Type.INVENTORY_ITEM,
id: itemId,
isDynamic: false
});In standard mode, field and sublist operations are not modeled as interactive UI edits. The script can read values directly and make changes without following the same sourcing sequence a user experiences on the form.
Dynamic mode is appropriate when the script needs UI-like behavior, such as selecting a sublist line, setting a value, sourcing dependent fields, and committing the line:
const itemRecord = record.load({
type: record.Type.INVENTORY_ITEM,
id: itemId,
isDynamic: true
});
const locationCount = itemRecord.getLineCount({
sublistId: 'locations'
});
for (let line = 0; line < locationCount; line += 1) {
itemRecord.selectLine({
sublistId: 'locations',
line
});
const locationId = itemRecord.getCurrentSublistValue({
sublistId: 'locations',
fieldId: 'location'
});
log.debug({
title: 'Item location',
details: locationId
});
itemRecord.commitLine({
sublistId: 'locations'
});
}Dynamic mode adds complexity because the script must select and commit lines. It also changes how sourcing and validation behave. For a simple field read, dynamic mode adds no benefit. We recommend standard mode unless the script’s logic genuinely depends on dynamic sublist interaction.
Reading item fields safely after loading
Use `getValue()` for the underlying field value and `getText()` when the script needs the display text for a select or record-reference field.
const itemType = itemRecord.getValue({
fieldId: 'type'
});
const subsidiaryText = itemRecord.getText({
fieldId: 'subsidiary'
});
const purchaseUnit = itemRecord.getText({
fieldId: 'purchaseunit'
});A field’s script ID is not always obvious from its screen label. The visible label “Item Name/Number” maps commonly to `itemid`, but custom forms, account features, and record types introduce exceptions. Confirm field IDs in the Records Browser or through a saved search before treating them as universal.
Item records also contain sublists that require different APIs. For example, location data is not read with `getValue()`. Use `getLineCount()` and `getSublistValue()` in standard mode:
const locationCount = itemRecord.getLineCount({
sublistId: 'locations'
});
for (let line = 0; line < locationCount; line += 1) {
const locationId = itemRecord.getSublistValue({
sublistId: 'locations',
fieldId: 'location',
line
});
const quantityAvailable = itemRecord.getSublistValue({
sublistId: 'locations',
fieldId: 'quantityavailable',
line
});
log.debug({
title: `Location line ${line}`,
details: {
locationId,
quantityAvailable
}
});
}The `locations` sublist and its fields depend on item configuration and enabled inventory features. A script should therefore handle zero lines and missing optional fields rather than assuming every item has location rows.
What causes “record does not exist” when loading an item?
The most common cause is using the wrong identifier. If the script passes an Item Name/Number where NetSuite expects an internal ID, `record.load()` cannot find the record.
Other causes include:
The item ID is empty, undefined, or a string containing unexpected characters.
The record type does not match the item subtype.
The item belongs to a context where the executing role lacks permission.
The item was deleted, inactivated, or removed between lookup and load.
A value came from an external system and was incorrectly treated as a NetSuite internal ID.
The script is running in a restricted subsidiary or location context.
The deployment is using a role that cannot view the required item record.
A useful diagnostic pattern is to log the record type and ID separately without exposing unnecessary item data:
log.debug({
title: 'Item load inputs',
details: {
type: record.Type.INVENTORY_ITEM,
id: itemId
}
});Do not conceal an identifier conversion problem by repeatedly retrying the load. Resolve the identifier first, validate the expected item type, and then perform one controlled load.
How to resolve an SKU to an internal ID first
When an integration starts with an SKU, search for the item before calling `record.load()`. A `search.create()` query can return the internal ID and item type:
define(['N/search', 'N/record'], (search, record) => {
const findItemByName = (itemName) => {
const result = search.create({
type: search.Type.ITEM,
filters: [
['itemid', 'is', itemName]
],
columns: [
'internalid',
'itemid',
'type'
]
}).run().getRange({
start: 0,
end: 1
})[0];
if (!result) {
throw new Error(`No NetSuite item matched: ${itemName}`);
}
return {
id: result.getValue({ name: 'internalid' }),
itemName: result.getValue({ name: 'itemid' }),
type: result.getValue({ name: 'type' })
};
};
return { findItemByName };
});The returned `type` value may not map directly to the constant required by `record.load()`. For a production integration, create a deliberate mapping layer and test every item subtype the integration is allowed to process. Do not silently default all search results to inventory items.
Exact matching also matters. A partial match can return a related SKU, matrix parent, or similarly named item. If item names are not unique in the relevant account configuration, add additional filters such as subsidiary, item type, or external ID.
Governance and performance considerations
Record loads consume script governance units. The exact cost depends on the record category and NetSuite’s current governance rules, so review the official SuiteScript documentation for the deployed account and version. The practical rule remains stable: do not load the same item repeatedly inside a large loop when one load or one search can provide the required data.
For batch processing, choose the retrieval method based on the work:
Use `record.load()` when you need record fields, sublists, or record APIs.
Use a saved search or `N/search` when you need a small set of fields across many items.
Use SuiteQL when you need structured, multi-record querying and the required data source supports it.
Use `N/record.submitFields()` for targeted body-field updates that do not require loading the entire record.
Cache repeated item IDs during one script execution.
Loading an item to read one body field is inefficient when the same field could come from a search result. Conversely, using SuiteQL for a process that must edit sublists leads to unnecessary complexity because query results are not editable record objects.
A reliable implementation pattern
A dependable item loader separates input validation, identifier resolution, record loading, and field extraction. That separation makes failures easier to diagnose and prevents an integration from mixing SKU values with internal IDs.
The loader should:
Confirm that an ID exists and is in the expected format.
Use the correct item record type.
Load in standard mode unless dynamic behavior is required.
Read only the fields and sublists needed by the business process.
Handle permission and missing-record errors explicitly.
Avoid saving unless the script intentionally changes the item.
This pattern is particularly important when item data moves between NetSuite and fulfillment, warehouse, ecommerce, or shipping systems. Consistent item identifiers are necessary for SKU mapping, inventory availability, unit-of-measure handling, and fulfillment accuracy. If the integration begins with an external item code, document the conversion from external identifier to NetSuite internal ID as part of the interface contract.
If item loading is part of a broader integration or script redesign, contact Versich to discuss your NetSuite requirements. A technical review should examine record types, permissions, governance usage, identifier mapping, and error handling together rather than treating `record.load()` as an isolated code change.
Conclusion
Loading an item record by ID in NetSuite is straightforward when the script uses the internal ID, the correct item subtype, and an appropriate record mode. The safest implementation validates the input, resolves external identifiers before loading, reads fields through the correct APIs, and avoids unnecessary record loads in loops.
For simple field retrieval, searches or SuiteQL provide a more efficient option. For sublists, record-level logic, and controlled updates, `record.load()` remains the right tool. Treating item type and identifier mapping as explicit parts of the design prevents the most common errors and creates a more reliable SuiteScript integration.
