AdvancedOracle NetSuiteSuiteScript 2.x / User Event and Client Scripts

Choosing between User Event and Client scripts in NetSuite

Question
when to use user event script vs client script in netsuite

Also searched as

  • netsuite beforeSubmit vs client script saveRecord
  • netsuite user event script server side validation
  • netsuite client script fieldChanged vs user event
  • netsuite user event afterSubmit vs client script

Short answer

User Event scripts (beforeLoad, beforeSubmit, afterSubmit) run server-side on every save regardless of how the record was created, including CSV import, web services and workflows, and are the only reliable place to enforce business rules. Client scripts (pageInit, fieldChanged, saveRecord, postSourcing) run only in an interactive browser session and can be bypassed entirely by any non-UI record save, so they should handle UX (field visibility, warnings, defaults) rather than validation that must never be skipped.

Applies to: SuiteScript 2.x User Event and Client Script types, all NetSuite editions and all record creation paths (UI, CSV import, SuiteScript, SuiteTalk, workflows)

Decide which script type owns which behavior

  1. 1If a rule must hold true no matter how the record is created (UI, CSV import, integration, workflow action), put it in a User Event script's beforeSubmit, since that is the one entry point every save path goes through.
  2. 2If a rule is purely about interactive usability, such as showing/hiding a field, popping a confirmation dialog, or auto-populating a field as the user types, put it in a Client script's fieldChanged or pageInit; these never run for a CSV import or API call.
  3. 3Never rely on a Client script's saveRecord function alone to block an invalid save; treat it as a first line of UX feedback and always duplicate the actual validation in a User Event beforeSubmit so non-UI paths cannot slip through.
  4. 4Use beforeLoad for read-only UI adjustments that need to happen before the page renders, such as removing a button or setting a field to display-only based on role or record state.
  5. 5Use beforeSubmit for validation and last-chance field manipulation before the record is committed to the database; context.newRecord and context.oldRecord are both available here for comparing changes.
  6. 6Use afterSubmit for anything that needs the record's final, committed internal ID, or that triggers side effects on other records (creating a related record, calling an external API, updating a linked transaction).
  7. 7In Client scripts, use postSourcing rather than fieldChanged when you need to react after NetSuite's automatic field sourcing has finished populating dependent fields, to avoid reading stale values.
  8. 8Check context.type in User Event scripts (create, edit, delete, xedit, copy) and skip logic that should not apply to inline edits or mass updates if the rule only makes sense on full record saves.

Why Client scripts alone are not enough for validation

A Client script only executes inside a browser session where NetSuite's UI is loaded and the script deployment applies to that form. A record created through CSV import, a Suitelet posting directly to record.save(), a RESTlet, SuiteTalk, or a scheduled script never triggers any Client script logic at all, so any business rule enforced only there is trivially bypassed by every non-interactive path.

This is the single most common design mistake in new NetSuite customizations: a saveRecord function that looks like it blocks bad data, until an integration or a bulk CSV import creates records that violate the same rule with no error, because the Client script simply never ran.

The safe pattern is to treat Client scripts as user experience only, and to duplicate any rule that must always hold in a User Event beforeSubmit, which does run for every save path without exception.

beforeLoad, beforeSubmit and afterSubmit in practice

beforeLoad runs when the record is being rendered for view/edit/create in the UI (and for print/pdf/email contexts too, distinguished by context.type), and is the right place for UI-only adjustments like context.form.getField(...).updateDisplayType(...); it has no effect on records saved without going through that render path.

beforeSubmit runs on every save right before the database write, with both context.newRecord (the record as it will be saved) and, for edits, context.oldRecord (the record's prior state) available, making it the natural place to compare old versus new values and block or correct a change.

afterSubmit runs after the record is committed and has a real internal ID, which matters for anything that needs to reference the just-saved record from elsewhere, such as creating a linked custom record or firing a webhook; trying to do this kind of work in beforeSubmit before the ID exists is a common source of bugs.

define(['N/record'], function(record) {
  function beforeSubmit(context) {
    var newRec = context.newRecord;
    var amount = newRec.getValue({ fieldId: 'total' });
    if (amount > 100000 && !newRec.getValue({ fieldId: 'custbody_vp_approved' })) {
      throw new Error('Orders over 100000 require VP approval before saving.');
    }
  }
  return { beforeSubmit: beforeSubmit };
});

context.type and avoiding unintended triggers

User Event scripts fire for xedit (inline grid edit) and massupdate contexts too, not just full record edits, which surprises developers whose logic assumes a fully loaded record with every field populated as it would be from the main edit form. Checking context.type and returning early for contexts the logic was not designed for avoids subtle bugs on inline edits.

Similarly, a copy context (using the UI's Make Copy) triggers beforeSubmit and afterSubmit as if it were a create, which is usually desired for default values but can be wrong for logic that assumes a first-time-ever creation, such as sending a one-time welcome notification.

Common pitfalls

  • !Enforcing a required business rule only in a Client script's saveRecord, letting CSV imports and integrations bypass it entirely.
  • !Reading a sourced field's value in fieldChanged before NetSuite finishes sourcing, getting a stale or empty value instead of using postSourcing.
  • !Doing heavy record.load or search calls in beforeLoad on a frequently viewed list, slowing down page render for every user who opens that record type.
  • !Not distinguishing context.type in a User Event script, so mass updates or inline edits trigger logic meant only for full-form saves.
  • !Trying to reference the record's internal ID inside beforeSubmit on a create, when it does not exist yet; that has to wait for afterSubmit.
  • !Deploying a Client script only to the form used in the UI while forgetting that other forms (or the mobile/CSV paths) for the same record type need the same protection at the User Event level anyway.

How an ERP-grounded AI assistant handles this

When reviewing a customization, ERPray can trace which record creation paths (UI form, CSV import map, integration, workflow action) actually exist for a given record type in a customer's account, and flag validation logic that lives only in a Client script where a non-UI path would bypass it. That grounded, account-specific check is more reliable than a generic code review checklist.

Frequently asked questions

Do Client scripts run during a CSV import?

No. CSV import writes records directly and does not load the record edit form, so no Client script deployment fires. Any validation that also needs to apply to CSV-imported records must live in a User Event script instead.

Can a User Event script cancel a save the way a Client script can?

Yes, throwing an error inside beforeSubmit prevents the record from being saved and surfaces the error message to whatever initiated the save, whether that is the UI, a CSV import row, or an API call, which is exactly why it is the reliable place for hard validation.

Should beforeLoad ever contain validation logic?

No, beforeLoad only affects how the record is displayed before any save happens; it cannot block or alter what gets submitted. Validation belongs in beforeSubmit, while beforeLoad is for read-only UI adjustments like hiding fields or buttons.

Why does my User Event script behave differently on inline edit than on the full form?

Inline edits (xedit context) submit only the changed field rather than the full record, so context.newRecord may not have every field populated the way a full-form save would. Check context.type and handle xedit separately if the logic depends on fields that are not part of the inline edit.

Related

Advanced

N/query versus N/search: choosing the right SuiteScript data access module

N/query runs SuiteQL directly against NetSuite's relational schema and is generally cheaper and faster for joins, aggregation and large filtered pulls, while N/search wraps the same saved-search engine used by the UI and is better when you need formula columns, summary types or a saved search object you can reuse. Both consume governance units per page fetched, but N/query's ability to express a join in one query often beats the multiple round trips a search-based join pattern requires.

Advanced

How NetSuite scheduled script queueing actually works

A Scheduled Script deployment queued behind other jobs is not failing; NetSuite runs a limited number of scheduled and Map/Reduce script queues concurrently per account, so deployments wait their turn based on account concurrency and deployment priority. The practical fixes are staggering trigger times, setting deployment priority correctly, and designing scripts that reschedule themselves cleanly with N/task instead of running one long unbroken execution.

Advanced

Avoiding governance limit errors in NetSuite Map/Reduce scripts

A NetSuite Map/Reduce script gives each map or reduce invocation of a key its own fresh governance allotment instead of sharing one pool across the whole run, so most SSS_USAGE_LIMIT_EXCEEDED failures come from a single key doing too much work, not from the total record count. Fix it by moving heavy logic out of getInputData, keeping map and reduce functions idempotent and cheap per key, and checking runtime.getCurrentScript().getRemainingUsage() before expensive calls.

Error fix

Fix NetSuite SSS_USAGE_LIMIT_EXCEEDED Error

SSS_USAGE_LIMIT_EXCEEDED fires when a SuiteScript execution consumes all the governance units (usage points) allotted to its script type before it finishes. Fix it by checking runtime.getCurrentScript().getRemainingUsage() before expensive calls, yielding or rescheduling in Scheduled scripts, and moving heavy record-count work into Map/Reduce, which yields automatically across stages.

Error fix

Fix NetSuite INVALID_FLD_VALUE Error

INVALID_FLD_VALUE means NetSuite rejected a value you tried to set on a field because it does not match the field's expected type, list option, or reference record. Fix it by confirming the internal ID or text value actually exists on that field's source list and matches the field's value type (text versus list versus record reference) before setting it.

Error fix

Fix NetSuite SSS_MISSING_REQD_ARGUMENT Error

SSS_MISSING_REQD_ARGUMENT means a NetSuite API call, most often record.create(), record.load(), or search.create(), was called without a parameter that method requires, such as type or id. Fix it by checking the object literal you passed against the current SuiteScript 2.x API signature and confirming no required key is undefined at runtime.

AI for ERP

AI for NetSuite, Beyond the Built-In Text Tools

NetSuite's built-in AI covers text generation, not grounded answers on your own data. See how a private LLM over SuiteQL adds real Q&A and controls.

AI for ERP

AI agents for NetSuite manufacturing operations

AI agents for NetSuite manufacturing: WIP tracking, routing exceptions, and work order status grounded in SuiteQL, with human approval on anything that writes back.

Stuck on Oracle NetSuite?

Talk to engineers who work inside Oracle NetSuite every week, and who build private AI that answers these questions from your own ERP data.