# SuiteScript 2.1 User Event Script Skeleton

> Source: https://netray.co/tools/suitescript-21-user-event-script-skeleton
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: Free Tools

## Short answer

A copy-ready SuiteScript 2.1 user event script with the JSDoc annotations, entry points, context checks and error logging you need to start.

## Category

netsuite

## Erp

NetSuite

## Audience

- NetSuite developer
- SuiteScript developer
- Technical consultant

## Tool

**Kind:** template

**Output:** code

**Language:** javascript

**Fields:**

#### Script name

**Key:** script_name

**Type:** text

**Default:** Sales Order Credit Check

#### Author

**Key:** author

**Type:** text

**Default:** Your Name

#### Record type (record.Type constant)

**Key:** record_type

**Type:** text

**Default:** SALES_ORDER

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

#### Field to read

**Key:** field_id

**Type:** text

**Default:** custbody_credit_hold

**Help:** Script ID of the field the script reads.

#### Version date

**Key:** date

**Type:** date

**Default:** 2026-10-20

**Blocks:**

#### user_event_skeleton.js

**Body:** /**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 * @NModuleScope SameAccount
 * @name {{script_name}}
 * @author {{author}}
 * @version 1.0 ({{date}})
 */
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: '{{field_id}}' });
      log.debug({ title: 'beforeSubmit {{record_type}}', 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.{{record_type}}, 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

**Body:** 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.

**Notes:** 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.

## Content

**Intro:** 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.

**Sections:**

#### How to use the skeleton

**Body:** 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

**Body:** 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.

**Bullets:**

- 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

**Body:** 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

**Body:** 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.

**Cta:** 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.

**Faq:**

#### What is the difference between SuiteScript 2.0 and 2.1?

**Answer:** 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?

**Answer:** 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?

**Answer:** 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?

**Answer:** 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.

## Search terms

suitescript 2.1 user event script, suitescript user event template, netsuite user event script example, suitescript aftersubmit example, suitescript 2.1 boilerplate, netsuite beforesubmit example, suitescript 2.1 skeleton

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
