🔹 What’s a subrecord?
A subrecord is a mini-record embedded inside another record. You don’t open it from the global menu; instead you access it through a field on a body or through a sublist line. Common examples:
- Address subrecord on a Customer (via the
addressbook
sublist) - Inventory Detail subrecord on transaction item lines (fulfillments, receipts, adjustments)
- (Others exist—payment instruments, tax details, etc.)
Key APIs you’ll use:
- Body level:
hasSubrecord({ fieldId })
getSubrecord({ fieldId })
createSubrecord({ fieldId })
removeSubrecord({ fieldId })
- Sublist line level:
getSublistSubrecord({ sublistId, fieldId, line })
getCurrentSublistSubrecord({ sublistId, fieldId })
createCurrentSublistSubrecord({ sublistId, fieldId })
removeSublistSubrecord({ sublistId, fieldId, line })
Tip: Subrecord field IDs differ by context. For addresses it’s usually
addressbookaddress
. For inventory it’sinventorydetail
.
🔹 Example 1 — Read the first Address subrecord on a Customer
Goal: Load a Customer, read the first address from the addressbook
sublist, and log it.
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/record'], (record) => {
const execute = () => {
try {
const customerId = 123; // <-- replace with a real Customer internal ID
// 1) Load the parent record (Customer)
const cust = record.load({
type: record.Type.CUSTOMER,
id: customerId,
isDynamic: false // dynamic not required for reading, but fine either way
});
// 2) Find how many address lines exist on the addressbook sublist
const addrCount = cust.getLineCount({ sublistId: 'addressbook' });
if (addrCount === 0) {
log.debug('Addresses', 'No addressbook lines found.');
return;
}
// 3) Access the first line’s Address subrecord
// - sublistId: 'addressbook' (the Customer address sublist)
// - fieldId: 'addressbookaddress' (the actual Address subrecord)
const addressSubrec = cust.getSublistSubrecord({
sublistId: 'addressbook',
fieldId: 'addressbookaddress',
line: 0
});
// 4) Read common address fields (field IDs are part of the Address subrecord)
const addr1 = addressSubrec.getValue({ fieldId: 'addr1' }); // Street 1
const city = addressSubrec.getValue({ fieldId: 'city' });
const state = addressSubrec.getValue({ fieldId: 'state' });
const zip = addressSubrec.getValue({ fieldId: 'zip' });
const country = addressSubrec.getValue({ fieldId: 'country' }); // 2-letter ISO (e.g., US, CA)
log.debug('Address[0]',
`addr1=${addr1}, city=${city}, state=${state}, zip=${zip}, country=${country}`);
} catch (e) {
log.error('Error reading address subrecord', e.message);
}
};
return { execute };
});
What beginners should notice:
- You don’t search for an “Address” record type—you reach into the parent record’s sublist field to get the subrecord.
- Subrecord fields (
addr1
,city
, etc.) are read via the subrecord object, not the parent.
🔹 Example 2 — Add a new Address to a Customer (create subrecord on a new sublist line)
Goal: Insert a new line in addressbook
, get its addressbookaddress
subrecord, write fields, and save.
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/record'], (record) => {
const execute = () => {
try {
const customerId = 123; // <-- replace with a real Customer ID
// 1) Load the Customer in DYNAMIC mode to work with sublist lines easily
const cust = record.load({
type: record.Type.CUSTOMER,
id: customerId,
isDynamic: true
});
// 2) Start a new line in the 'addressbook' sublist
cust.selectNewLine({ sublistId: 'addressbook' });
// Optional: mark as default shipping/billing at the addressbook line level
cust.setCurrentSublistValue({
sublistId: 'addressbook',
fieldId: 'defaultshipping',
value: true
});
cust.setCurrentSublistValue({
sublistId: 'addressbook',
fieldId: 'defaultbilling',
value: false
});
// 3) Create or get the Address subrecord on the CURRENT line
// If no subrecord exists yet, create it for this line:
const addrSubrec = cust.createCurrentSublistSubrecord({
sublistId: 'addressbook',
fieldId: 'addressbookaddress'
});
// 4) Set address fields on the subrecord (these are subrecord fields)
addrSubrec.setValue({ fieldId: 'addr1', value: '123 Demo Street' });
addrSubrec.setValue({ fieldId: 'city', value: 'Toronto' });
addrSubrec.setValue({ fieldId: 'state', value: 'ON' });
addrSubrec.setValue({ fieldId: 'zip', value: 'M5H 2N2' });
addrSubrec.setValue({ fieldId: 'country', value: 'CA' }); // Canada
// 5) IMPORTANT: Commit the subrecord back to the line
addrSubrec.save(); // persists the subrecord to the current line context
// 6) Commit the sublist line itself
cust.commitLine({ sublistId: 'addressbook' });
// 7) Save parent record
const id = cust.save();
log.debug('Customer updated', `New address added. Customer ID: ${id}`);
} catch (e) {
log.error('Error creating address subrecord', e.message);
}
};
return { execute };
});
Key takeaways:
- Use dynamic mode for easier sublist editing (
selectNewLine
,commitLine
). - You save the subrecord, then commit the line, then save the parent.
🔹 Example 3 — Inventory Detail subrecord on an Item Fulfillment line (assign Lot/Serial/Bin)
Goal: On an Item Fulfillment, open an item line and write Inventory Detail assignments
(lot/serial numbers, bins, and quantities). The exact required fields depend on your item type and enabled features (Lot vs Serial, Bins enabled or not).
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/record'], (record) => {
const afterSubmit = (context) => {
try {
if (context.type !== context.UserEventType.CREATE &&
context.type !== context.UserEventType.EDIT) return;
// 1) Load the Item Fulfillment in DYNAMIC mode
const ifId = context.newRecord.id;
const fulfil = record.load({
type: record.Type.ITEM_FULFILLMENT,
id: ifId,
isDynamic: true
});
// 2) Find line to edit on the 'item' sublist (example assumes line 0)
const lineCount = fulfil.getLineCount({ sublistId: 'item' });
if (lineCount === 0) {
log.debug('Item Fulfillment', 'No item lines to assign.');
return;
}
// Select the first line (adjust as needed or loop over all lines)
fulfil.selectLine({ sublistId: 'item', line: 0 });
// 3) Create or get the Inventory Detail subrecord for this line
// If there is no inventory detail yet, create it on the current line:
let invDetail;
try {
invDetail = fulfil.getCurrentSublistSubrecord({
sublistId: 'item',
fieldId: 'inventorydetail'
});
} catch (_) {
invDetail = fulfil.createCurrentSublistSubrecord({
sublistId: 'item',
fieldId: 'inventorydetail'
});
}
// 4) Inside Inventory Detail subrecord there is a sublist: 'inventoryassignment'
// Each line represents a lot/serial/bin assignment with a quantity.
// Clear existing lines if needed (optional)
const assignCount = invDetail.getLineCount({ sublistId: 'inventoryassignment' });
for (let i = assignCount - 1; i >= 0; i--) {
invDetail.removeLine({ sublistId: 'inventoryassignment', line: i });
}
// 5) Add a new assignment line
invDetail.selectNewLine({ sublistId: 'inventoryassignment' });
// Common fields (depends on setup):
// - For LOT items: 'issueinventorynumber' (the lot number to issue)
// - For SERIAL items:'issueinventorynumber' (serial number), quantity usually 1 per line
// - If BINS enabled: 'binnumber' (internal ID of the bin)
// Example: assign from Bin internal ID 7, Lot "LOT-ABC-001", with quantity 3
invDetail.setCurrentSublistValue({
sublistId: 'inventoryassignment',
fieldId: 'binnumber', // only if bins are enabled and bins required
value: 7 // <-- replace with actual Bin internal ID
});
invDetail.setCurrentSublistValue({
sublistId: 'inventoryassignment',
fieldId: 'issueinventorynumber',
value: 'LOT-ABC-001' // <-- replace with actual Lot/Serial number
});
invDetail.setCurrentSublistValue({
sublistId: 'inventoryassignment',
fieldId: 'quantity',
value: 3 // For serial items, this is typically 1 per line
});
// Commit the inventory assignment line
invDetail.commitLine({ sublistId: 'inventoryassignment' });
// 6) Save the Inventory Detail subrecord back to the line
invDetail.save();
// 7) Commit the 'item' line and save the parent fulfillment
fulfil.commitLine({ sublistId: 'item' });
const savedId = fulfil.save();
log.debug('Inventory Detail', `Assignments saved on IF ${savedId}`);
} catch (e) {
log.error('Error setting Inventory Detail', e.message);
}
};
return { afterSubmit };
});
Important notes:
- Field IDs (
issueinventorynumber
,binnumber
) may vary by item type and features. - Serial-numbered items: you usually need one line per serial with qty
1
. - Lot-numbered items: one line can carry qty > 1 for the same lot.
- If you get “invalid subrecord operation,” verify:
- The item on that line actually requires inventory detail
- You’re using the correct subrecord/sublist field IDs
- You’re in dynamic mode when editing sublist lines
🔹 Troubleshooting & Tips
- “You have attempted an invalid subrecord or sublist operation”
Ensure you used the correct method:- Body subrecord →
getSubrecord/createSubrecord
- Sublist subrecord →
get(Sublist|CurrentSublist)Subrecord
- Body subrecord →
- Always save/commit in the right order:
- Save subrecord → commit the line → save the parent.
- Check features: If Inventory Detail isn’t required or bins aren’t enabled, the field may be missing.
- Use logs: Add
log.debug()
before/after each critical step.
âś… Key Takeaway
Subrecords are embedded data structures you access through parent record fields or sublist lines. Mastering the subrecord APIs lets you build precise automations like address management and inventory assignments—cleanly and reliably.