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.
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.
Related Tools
SuiteQL Query Generator
Fill in a table, columns, filter and sort and get a SuiteQL statement ready for the N/query module, the REST SuiteQL endpoint or the SuiteAnalytics Workbook.
NetSuiteSuiteScript Map/Reduce Skeleton Generator
A SuiteScript 2.1 map/reduce template with a SuiteQL input stage, map and reduce handlers and a summarize stage that reports every error.
NetSuiteNetSuite Governance Unit Calculator
Estimate how many governance units a script run uses and how many records fit in one execution before you hit the limit.
NetSuiteNetSuite Internal ID Lookup Worksheet
An editable table to record internal IDs, script IDs and external IDs per environment, so scripts and integrations stop hardcoding guesses.
More NetSuite tools
See all- NetSuite Saved Search Formula Generator
- NetSuite CSV Import Template Guide
- NetSuite Role Permission Matrix Worksheet
- NetSuite Inventory Costing Method Reference
- NetSuite Month End Close Checklist
- NetSuite License Cost Estimator
- NetSuite User Access Request Form Template
- NetSuite Custom Record Design Spec Template
- NetSuite SuiteScript Deployment Handover Template