How to Update a Customer by External ID Through a NetSuite RESTlet

Resolve an external ID to one internal ID, guard missing matches, update the customer phone and verify the same record in a local RESTlet exercise.

The quick answer

In a custom RESTlet, search the intended record type by external ID text, require exactly one result, then pass its internal ID to record.submitFields. An external ID string is not the id argument used by SuiteScript record methods.

This walkthrough runs the checked-in SuiteScript source against explicit local N/search and N/record mocks. It sends no production request, deploys no script and creates no customer. The native customer views show the local state before and after the update.

Before you start

The existing fictional customer is North Harbor Service. Its internal ID is 75001, its displayed Customer ID is CUST75001, and its external ID is CRM-CUST-104. These identifiers serve different purposes. Its phone starts at 202-555-0104 and will change to 202-555-0148.

In production, deploy and authenticate the RESTlet separately. Give the executing role access to the intended customer and permission to edit it. Account scripts, workflows, mandatory fields and field permissions can affect a real update. Keep the handler restricted to the record type and fields it is meant to edit.

This guide addresses a custom RESTlet. The SuiteTalk REST Record Service supports its own external-ID URL form, such as /customer/eid:CRM-CUST-104. That routing rule does not change the internal-ID requirement of N/record methods inside a RESTlet.

01. Confirm the existing customer

Open the customer with internal ID 75001. Confirm North Harbor Service, displayed Customer ID CUST75001, and Phone 202-555-0104.

Before: Phone 202-555-0104. Enlarge to verify North Harbor Service and Customer ID CUST75001.

The external ID need not appear as a field on this form. The local exercise's input data and subsequent readback establish CRM-CUST-104; the native view verifies the customer identity and visible phone. No new External ID field is added to the form.

02. Stop when the external ID is not found

The local test verifies that the displayed Customer search uses this text filter:

filters: [[
  'externalidstring',
  search.Operator.IS,
  data.externalId
]]

The Customer record browser distinguishes externalidstring, a text search filter, from externalid, a select-style filter. For the incoming text in this example, use the text filter with IS. Keep the value a string; converting a numeric-looking external ID to a number can lose leading zeros.

Fetch at most two results with getRange({start:0, end:2}). Zero results return the handler's CUSTOMER_NOT_FOUND error. More than one result returns CUSTOMER_EXTERNAL_ID_NOT_UNIQUE. These names belong to this example's validation. They are not claims about NetSuite's native error messages or that duplicate external IDs are normally allowed.

Run the local request with a missing key:

node request.mjs \
  --external-id=CRM-CUST-404
Missing key: zero matches, custom not-found error, original customer and phone unchanged, no creation. Local exercise only.

The trace reports Matches: 0. Fresh readback shows customer 75001 still has CRM-CUST-104 and Phone 202-555-0104. Customer count stays 1, with created:false. The handler stops before submitFields; it does not choose an arbitrary result or create a replacement customer.

03. Resolve the correct key and update by internal ID

Run the same local request with the correct external ID:

node request.mjs \
  --external-id=CRM-CUST-104

The search returns one result. The handler reads that result's internal ID and uses it in the update:

const id = rows[0].getValue({
  name: 'internalid'
});
record.submitFields({
  type: record.Type.CUSTOMER,
  id,
  values: {phone: data.phone},
  options: {
    enableSourcing: false,
    ignoreMandatoryFields: false
  }
});
One match resolves internal ID 75001. Fresh local readback confirms the same external ID, new phone and unchanged customer count.

The local trace shows submitFields internal ID: 75001. Fresh readback confirms the same company and external ID, phone 202-555-0104 → 202-555-0148, and unchanged customer count. This is evidence of the local harness execution, not a live NetSuite update or authentication result.

submitFields updates supported body fields without loading the record. Customer Phone supports this operation. Sublist line fields and subrecord fields require another method; do not generalize the phone example to every field. If you use record.load instead, its id also takes the internal ID, followed by the appropriate edits and save.

04. Verify the same customer after the update

Follow Open the same customer. The local URL still identifies internal ID 75001. Confirm North Harbor Service, CUST75001, and the new Phone 202-555-0148.

After: Phone 202-555-0148. Enlarge to verify the same North Harbor Service customer and CUST75001.

Compare this with the starting frame. The visible identity is unchanged and the local update targets only phone. Live workflows or user events could also change fields. The preceding fresh readback verifies that CRM-CUST-104 also remains unchanged. In a real integration, read back the saved record and inspect the actual response before treating an update as complete.

Common questions

Can record.submitFields use an external ID directly?

Its id parameter expects the internal ID of the existing record. Resolve the external ID to an internal ID first. A string value is allowed for that parameter, but that does not make an external ID interchangeable with an internal ID.

Can record.load load a customer by external ID?

Its documented id parameter takes the internal ID. Search by the external ID, check the result count, and load the resolved internal ID when a load-edit-save operation is needed.

Is externalid the same search filter as externalidstring?

No. The Customer record browser lists externalid as select and externalidstring as text. This example uses externalidstring IS for an exact incoming text lookup. Verify the search fields and operators for the specific record type you are using.

Why fetch two results when I only need one customer?

Two is enough to distinguish one match from more than one. The defensive guard prevents an arbitrary first-result update. Zero matches also stop before mutation. These guards complement the account's actual external-ID uniqueness rules.

Does a missing match create a customer?

This handler is update-only. A missing match returns a controlled error and performs no update or creation. Creation or upsert would need a separately designed contract with its own permissions and validation.

Does the source forum's missing id error prove the filter was wrong?

No. The thread did not establish the complete original root cause. The documented requirement is that record methods receive an internal ID. The safe lookup-and-guard pattern prevents an empty result from flowing into an undefined id without claiming it reproduces that particular account failure.

Why does eid work in a REST URL but not in record.submitFields?

The REST Record Service interprets its own URL addressing syntax. A custom RESTlet executes SuiteScript and controls its own payload contract. Its record methods still require internal IDs.

What happens with blank input or different letter case?

The handler rejects missing, null or empty strings before searching. It passes other strings unchanged. The local mock compares exact text without trimming or case folding; that is a test boundary, not a claim about live search collation. Oracle documents case-insensitive external IDs for web-services references. Define and test whitespace and case behavior for your actual integration.

What if the phone already has the requested value?

This example still submits the targeted phone value and returns success. Its fresh readback can show identical before and after values. It has no separate no-op shortcut.

Supporting sources