Writing / Netsuite

SuiteScript Standard vs Dynamic Mode: Which Sublist APIs to Use

Choose the correct SuiteScript sublist APIs for standard and dynamic record modes, with working examples, field-sourcing differences and a debugging checklist.

SuiteScript provides two different ways to edit record sublists. The APIs look similar, but they are not interchangeable.

In standard mode, set a value by specifying its line number:

record.setSublistValue({
  sublistId: 'item',
  fieldId: 'quantity',
  line: 0,
  value: 2,
});

In dynamic mode, select a line, edit its current values, and commit it:

record.selectLine({ sublistId: 'item', line: 0 });
record.setCurrentSublistValue({
  sublistId: 'item',
  fieldId: 'quantity',
  value: 2,
});
record.commitLine({ sublistId: 'item' });

Mixing those two patterns is a common cause of SSS_INVALID_SUBLIST_OPERATION, uncommitted lines and values that appear to be overwritten.

The short answer

Record mode Read a known line Set a known line Add a line
Standard getSublistValue() setSublistValue() Set values at the next line number
Dynamic selectLine() then getCurrentSublistValue() selectLine() then setCurrentSublistValue() selectNewLine(), set current values, then commitLine()

Use standard mode when you want predictable, index-based updates. Use dynamic mode when you need NetSuite to behave more like the form in the user interface, including real-time sourcing and line validation.

How the record mode is chosen

Records created or loaded with N/record use standard mode unless isDynamic: true is supplied:

const salesOrder = record.load({
  type: record.Type.SALES_ORDER,
  id: salesOrderId,
  isDynamic: true,
});

You can confirm the current mode through the record’s isDynamic property:

log.debug({
  title: 'Record mode',
  details: salesOrder.isDynamic ? 'dynamic' : 'standard',
});

User Event newRecord and oldRecord objects always operate in standard mode. Setting isDynamic in an entry-point annotation does not change that. If dynamic behaviour is essential, load the record separately with record.load({ isDynamic: true }).

Updating a sublist in standard mode

Standard mode does not have a selected current line. Each call identifies the line directly.

const salesOrder = record.load({
  type: record.Type.SALES_ORDER,
  id: salesOrderId,
  isDynamic: false,
});

const lineCount = salesOrder.getLineCount({
  sublistId: 'item',
});

for (let line = 0; line < lineCount; line += 1) {
  const itemId = salesOrder.getSublistValue({
    sublistId: 'item',
    fieldId: 'item',
    line,
  });

  if (String(itemId) === String(targetItemId)) {
    salesOrder.setSublistValue({
      sublistId: 'item',
      fieldId: 'quantity',
      line,
      value: 2,
    });
  }
}

salesOrder.save({
  enableSourcing: true,
  ignoreMandatoryFields: false,
});

Oracle documents Record.setSublistValue() as a standard-mode method. There is no selectLine() or commitLine() step.

Standard mode is usually the clearer option for bulk updates because every operation names its target line explicitly. NetSuite processes sourcing and calculations when the record is saved instead of reproducing the UI sequence after every value.

Adding a line in standard mode

Use the current line count as the index of the new line:

const line = salesOrder.getLineCount({ sublistId: 'item' });

salesOrder.setSublistValue({
  sublistId: 'item',
  fieldId: 'item',
  line,
  value: itemId,
});

salesOrder.setSublistValue({
  sublistId: 'item',
  fieldId: 'quantity',
  line,
  value: 2,
});

You do not call selectNewLine() or commitLine() in this mode.

Updating a sublist in dynamic mode

Dynamic mode has a current line, similar to editing a transaction in the NetSuite UI.

const salesOrder = record.load({
  type: record.Type.SALES_ORDER,
  id: salesOrderId,
  isDynamic: true,
});

const lineCount = salesOrder.getLineCount({ sublistId: 'item' });

for (let line = 0; line < lineCount; line += 1) {
  salesOrder.selectLine({
    sublistId: 'item',
    line,
  });

  const itemId = salesOrder.getCurrentSublistValue({
    sublistId: 'item',
    fieldId: 'item',
  });

  if (String(itemId) !== String(targetItemId)) {
    continue;
  }

  salesOrder.setCurrentSublistValue({
    sublistId: 'item',
    fieldId: 'quantity',
    value: 2,
  });

  salesOrder.commitLine({
    sublistId: 'item',
  });
}

salesOrder.save();

Record.setCurrentSublistValue() changes the field on the selected line. Record.commitLine() then commits that line to the record.

If you omit commitLine(), the current line may not be included in the saved record.

Adding a line in dynamic mode

The sequence matters:

salesOrder.selectNewLine({
  sublistId: 'item',
});

salesOrder.setCurrentSublistValue({
  sublistId: 'item',
  fieldId: 'item',
  value: itemId,
});

salesOrder.setCurrentSublistValue({
  sublistId: 'item',
  fieldId: 'quantity',
  value: 2,
});

salesOrder.commitLine({
  sublistId: 'item',
});

Set fields in the same logical order that you would use in the UI. Selecting an item can source its description, price, units, tax details and other dependent values. A later sourcing event can replace a value that was set too early.

Sourcing is the important behavioural difference

Standard mode defers much of its sourcing, validation and calculation until save(). The order of most setter calls is therefore less important.

Dynamic mode performs that work as fields are changed. This is useful when later code needs to inspect a sourced value before saving, but it also makes field order significant.

For example, this dynamic sequence can produce an unexpected rate:

salesOrder.setCurrentSublistValue({
  sublistId: 'item',
  fieldId: 'rate',
  value: 25,
});

salesOrder.setCurrentSublistValue({
  sublistId: 'item',
  fieldId: 'item',
  value: itemId,
});

Selecting the item can source the price and replace the earlier rate. Set the item first, allow its dependent fields to source, and then apply intentional overrides.

forceSyncSourcing and ignoreFieldChange

Dynamic setters provide two options that are often misunderstood:

salesOrder.setCurrentSublistValue({
  sublistId: 'item',
  fieldId: 'item',
  value: itemId,
  forceSyncSourcing: true,
  ignoreFieldChange: false,
});
  • forceSyncSourcing: true asks NetSuite to complete dependent-field sourcing synchronously. It can help when the next line of code must immediately read a sourced value.
  • ignoreFieldChange: true suppresses the normal field-change and secondary events. It does not mean “ignore validation” and should not be added automatically.

Use these flags to solve a specific sourcing or event problem, not as boilerplate.

This is different from a Suitelet sublist

The standard-versus-dynamic distinction applies to record objects from N/record and client currentRecord objects. It does not change how a Suitelet sublist created with N/ui/serverWidget is populated.

A server-rendered Suitelet sublist still uses its own method:

suiteletSublist.setSublistValue({
  id: 'custpage_quantity',
  line: 0,
  value: '2',
});

After a Suitelet POST, read submitted values through request.getLineCount() and request.getSublistValue(). See How to Read Suitelet Sublist Values After a POST for the complete pattern.

Common errors and what they usually mean

SSS_INVALID_SUBLIST_OPERATION

Check all of the following:

  • The sublist is editable for the record and execution context.
  • The line number exists.
  • A dynamic record has a selected current line.
  • The method matches the record mode.
  • The script is not trying to edit a read-only sublist.

The line was not saved

In dynamic mode, confirm that commitLine() ran after the final setter and before save().

A value was overwritten

In dynamic mode, inspect the order of setter calls. A later field may have sourced the value again. Set controlling fields such as customer, item or price level before dependent fields and overrides.

The script works in a Client Script but fails in a User Event

A Client Script works with the current UI record and is normally dynamic. User Event newRecord and oldRecord objects are standard mode. Replace current-line methods with the corresponding line-indexed methods, or explicitly load a separate dynamic record when that behaviour is required.

A debugging checklist

When a sublist edit behaves unexpectedly:

  1. Log record.isDynamic.
  2. Confirm the sublist ID, field ID and zero-based line number.
  3. Use setSublistValue() for standard mode.
  4. Use selectLine() or selectNewLine(), current-line setters and commitLine() for dynamic mode.
  5. In dynamic mode, set controlling fields before dependent values.
  6. Log the value immediately after setting it and again after later sourcing calls.
  7. Confirm the sublist is editable in the current record state and execution context.

The reliable rule is simple: standard mode edits a specified line; dynamic mode edits the selected current line. Once that distinction is explicit, most sublist API errors become straightforward to diagnose.