NetSuiteFree generatorPDF, Excel, Word exportNetSuite

SuiteScript 2.1 User Event Script Skeleton

A user event script runs on the server when a record is loaded, submitted or deleted. This skeleton gives you the correct JSDoc annotations and the three entry points with sensible guards so you do not start from a blank file.

Fill in

Constant name from N/record, for example SALES_ORDER, CUSTOMER, INVOICE.

Script ID of the field the script reads.

Generated code
Download:

user_event_skeleton.js

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 * @NModuleScope SameAccount
 * @name Sales Order Credit Check
 * @author Your Name
 * @version 1.0 (2026-10-20)
 */
define(['N/record', 'N/log', 'N/runtime'], (record, log, runtime) => {

  const beforeLoad = (context) => {
    try {
      if (context.type !== context.UserEventType.VIEW) return;
      // context.form is available here for adding fields or buttons
    } catch (e) {
      log.error({ title: 'beforeLoad failed', details: e });
    }
  };

  const beforeSubmit = (context) => {
    try {
      const rec = context.newRecord;
      const value = rec.getValue({ fieldId: 'custbody_credit_hold' });
      log.debug({ title: 'beforeSubmit SALES_ORDER', details: 'value=' + value });
      // Validate or set fields here. Do not call rec.save() in this entry point.
    } catch (e) {
      log.error({ title: 'beforeSubmit failed', details: e });
      throw e;
    }
  };

  const afterSubmit = (context) => {
    try {
      if (context.type === context.UserEventType.DELETE) return;
      if (runtime.executionContext === runtime.ContextType.MAP_REDUCE) return;
      const rec = record.load({ type: record.Type.SALES_ORDER, id: context.newRecord.id });
      log.audit({ title: 'afterSubmit', details: 'Loaded ' + rec.id });
      // Side effects such as creating related records or calling an integration go here.
    } catch (e) {
      log.error({ title: 'afterSubmit failed', details: e });
    }
  };

  return { beforeLoad, beforeSubmit, afterSubmit };
});

Deployment record checklist

Script file: upload to the File Cabinet under SuiteScripts.
Script record: Customization > Scripting > Scripts > New, select the file.
Deployment: set Applies To to the record type, Status to Testing, Audience to your test role.
After testing: change Status to Released and widen the audience.

Adjust the module list to what you use. The skeleton targets SuiteScript 2.1, which supports modern JavaScript syntax such as arrow functions and const. Test in a sandbox with Status set to Testing before releasing a deployment to all roles.

Customization review for NetSuite

Optional expert look at your user event and map/reduce scripts for governance, recursion and role risks.

Optional. The tool and every export work without this. No spam.

How to use the skeleton

Fill in the script name, author, record constant and the field the script reads, then copy the generated file into your project. Upload it to the File Cabinet, create a script record that points at it, and add a deployment for the record type. Keep the deployment in Testing status with a limited audience until you have verified behavior with real records and different roles.

What each entry point is for

beforeLoad runs when a record opens and is the place to add buttons or fields to the form. beforeSubmit runs before the data is written, so validate or default values there and avoid saving the record. afterSubmit runs after the write, which is the right place for creating related records or calling external services, because the record ID exists and a failure will not roll back the original save the same way.

  • beforeLoad: form changes, hidden fields, buttons
  • beforeSubmit: validation and defaulting on the new record
  • afterSubmit: related records, integrations, notifications
  • Check context.type to skip DELETE or specific modes

Governance and performance

User event scripts have a limit of 1000 governance units per execution. Loading and saving transaction records costs several units each, and a script that loads the record again in afterSubmit pays for it. Prefer context.newRecord for reading and use record.submitFields for small updates rather than load and save. Also guard against recursion: a script that updates the same record in afterSubmit can trigger itself.

Common mistakes

Frequent problems include calling save inside beforeSubmit, forgetting that the script also fires for CSV import and web services unless you check the execution context, swallowing errors so users never see why a save failed, and deploying to All Roles while still testing. Use log.debug sparingly in production, since heavy logging adds overhead and can truncate output.

Frequently Asked Questions

What is the difference between SuiteScript 2.0 and 2.1?

SuiteScript 2.1 supports newer JavaScript syntax such as arrow functions, let, const and async patterns, while 2.0 uses an older engine. The annotation changes from @NApiVersion 2.0 to 2.1. Most 2.0 code works in 2.1 after testing.

Can I call record.save in beforeSubmit?

No. Changes to context.newRecord in beforeSubmit are saved with the record automatically. Calling save on the same record there causes errors or loops. Use afterSubmit for side effects on other records.

How do I stop the script running on CSV import?

Read runtime.executionContext and compare it with runtime.ContextType values such as CSVIMPORT, WEBSERVICES or USERINTERFACE, then return early for contexts you do not want to handle.

How many governance units does a user event script get?

A user event script is limited to 1000 units per execution. Record loads, saves and searches consume units, so check the cost of your operations against the SuiteScript governance table for your record types.

If you maintain many NetSuite customizations, Netray can help build agents that review scripts and flag governance and recursion risks, with a human approving any change.