NetSuite SuiteScript is the JavaScript-based scripting platform that lets you customize, automate, and extend nearly every aspect of your NetSuite account. Whether you want to auto-populate fields on a form, run nightly data cleanup jobs, build REST APIs for third-party integrations, or validate data before it hits the database β SuiteScript is how you do it. This guide covers everything you need to get started and grow into advanced SuiteScript development in 2026.
What Is NetSuite SuiteScript?
SuiteScript is a server-side (and in some cases client-side) JavaScript framework built into the NetSuite platform. It gives developers programmatic access to NetSuite records, searches, workflows, emails, and even the UI. SuiteScript runs inside NetSuite’s sandboxed JavaScript engine, so you never need to host or manage any external infrastructure β your scripts are deployed directly to your NetSuite account and executed by the platform.
There are two versions of SuiteScript: SuiteScript 1.0 (the legacy API, now deprecated) and SuiteScript 2.0/2.1 (the modern, module-based API). All new development should use SuiteScript 2.1, which adds support for modern JavaScript features including let, const, arrow functions, template literals, destructuring, and async/await patterns.
SuiteScript Script Types: Which One Do You Need?
SuiteScript has over a dozen script types, each designed for a specific use case. Choosing the right script type is the first decision every SuiteScript developer makes. Here are the most important ones:
Client Script
Client Scripts run in the user’s browser when they interact with a NetSuite record form. They can respond to events like pageInit (when the page loads), fieldChanged (when a field value changes), validateField (before a field is committed), and saveRecord (before the form is submitted). Use Client Scripts for real-time field validation, auto-populating dependent fields, and enhancing the user experience on forms.
User Event Script
User Event Scripts run on the server when a record is created, updated, or deleted. They fire on three triggers: beforeLoad (when a record is loaded for viewing or editing), beforeSubmit (just before the record saves β ideal for validation and data transformation), and afterSubmit (after the record saves β ideal for triggering downstream actions). User Event Scripts are the most commonly used script type in NetSuite.
Scheduled Script
Scheduled Scripts run on a time-based schedule (hourly, daily, weekly, or custom CRON). They are ideal for batch processing, data sync jobs, report generation, and any task that should run automatically without a user triggering it. Scheduled Scripts have generous governance limits (10,000 units per execution) compared to other script types.
RESTlet
RESTlets expose custom REST API endpoints inside NetSuite. They support GET, POST, PUT, and DELETE HTTP methods and can accept and return JSON data. RESTlets are the go-to choice for integrating NetSuite with external systems like Magento, Shopify, Salesforce, or custom applications. They run with full SuiteScript access, so you can read and write any record type.
Map/Reduce Script
Map/Reduce Scripts are designed for processing large volumes of data in parallel. They divide work into stages (getInputData, map, shuffle, reduce, summarize) that NetSuite executes concurrently across multiple queues. If you need to process thousands of records β like a mass update, a large import, or a bulk export β Map/Reduce is far more efficient and reliable than a Scheduled Script.
Suitelet
Suitelets create custom UI pages inside NetSuite. You can build forms, lists, dashboards, and portlets that appear as native NetSuite pages. They’re useful for internal tools, approval workflows with custom interfaces, and data entry screens that don’t map cleanly to a standard NetSuite record.
Workflow Action Script
Workflow Action Scripts are called from within a SuiteFlow workflow. They let you add custom JavaScript logic as a workflow action β useful when the built-in workflow actions are not powerful enough for your business rule.
SuiteScript 2.1 Module System
SuiteScript 2.1 uses an AMD (Asynchronous Module Definition) module system. Every script starts with a define() call that declares which NetSuite modules it needs. NetSuite provides dozens of built-in modules, each covering a different area of functionality. The most commonly used ones are:
- N/record β Create, load, edit, and delete NetSuite records (Sales Orders, Customers, Inventory Items, etc.)
- N/search β Run saved searches and inline searches against any record type
- N/email β Send emails from within scripts, with support for attachments and templates
- N/runtime β Access runtime context: current user, role, script parameters, execution context
- N/log β Write debug, audit, and error messages to the script execution log
- N/https / N/http β Make outbound HTTP/HTTPS requests to external APIs
- N/file β Read and write files in the NetSuite File Cabinet
- N/query β Run SuiteQL queries using SQL-like syntax for complex data retrieval
- N/format β Format and parse dates, currencies, and other NetSuite field types
- N/ui/serverWidget β Build custom Suitelet UI forms, lists, and fields programmatically
Your First SuiteScript 2.1 Script: User Event Example
Here is a complete, working SuiteScript 2.1 User Event Script that fires on a Sales Order after it is saved. It checks if the order total exceeds $10,000 and sends an email notification to a manager:
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/record', 'N/email', 'N/runtime', 'N/log'], (record, email, runtime, log) => {
const afterSubmit = (context) => {
if (context.type !== context.UserEventType.CREATE) return;
const soRecord = context.newRecord;
const orderTotal = soRecord.getValue({ fieldId: 'total' });
const orderNumber = soRecord.getValue({ fieldId: 'tranid' });
const customerId = soRecord.getValue({ fieldId: 'entity' });
if (orderTotal >= 10000) {
log.audit({
title: 'High-Value Order Alert',
details: `Order ${orderNumber} total: $${orderTotal}`
});
email.send({
author: runtime.getCurrentUser().id,
recipients: ['manager@yourcompany.com'],
subject: `High-Value Order Alert: ${orderNumber}`,
body: `A new Sales Order (${orderNumber}) has been placed for $${orderTotal.toFixed(2)}. Please review.`
});
}
};
return { afterSubmit };
});
This script demonstrates the core SuiteScript 2.1 pattern: the @NApiVersion 2.1 and @NScriptType JSDoc annotations tell NetSuite what version and type this script is; the define() call imports the needed modules; and the exported function (afterSubmit) handles the specific event trigger.
SuiteScript Governance: Understanding Execution Limits
Every SuiteScript script type has a governance limit β a budget of “units” that controls how much work a script can do in a single execution. NetSuite enforces this to protect shared infrastructure. Understanding governance is critical for writing scripts that don’t fail in production.
Common governance costs: loading a record costs 10 units, searching costs 10 units per 1,000 results, sending an email costs 10 units, and making an HTTP request costs 10 units. A User Event Script has a 1,000-unit limit per execution, while a Scheduled Script gets 10,000 units and a Map/Reduce Script gets 10,000 units per reducer invocation.
The single most common governance mistake is loading records inside a loop. If you need to update 500 records, never load each one individually inside a loop β that alone would cost 5,000 units and exceed most script type limits. Instead, use record.submitFields() for targeted updates (costs only 10 units) or restructure with a Map/Reduce Script for bulk operations.
Working with Records: The N/record Module
The N/record module is the heart of SuiteScript development. Here are the four most common operations you will perform on NetSuite records:
Creating a Record
const newCustomer = record.create({
type: record.Type.CUSTOMER,
isDynamic: true
});
newCustomer.setValue({ fieldId: 'companyname', value: 'Acme Corp' });
newCustomer.setValue({ fieldId: 'email', value: 'contact@acme.com' });
newCustomer.setValue({ fieldId: 'subsidiary', value: 1 });
const customerId = newCustomer.save();
Loading and Editing a Record
const soRecord = record.load({
type: record.Type.SALES_ORDER,
id: 12345,
isDynamic: true
});
soRecord.setValue({ fieldId: 'memo', value: 'Updated by SuiteScript' });
const savedId = soRecord.save();
Updating Specific Fields (submitFields)
// Most efficient way to update one or two fields without loading the full record
record.submitFields({
type: record.Type.CUSTOMER,
id: 67890,
values: {
comments: 'VIP customer - priority handling',
category: 3
}
});
Deleting a Record
record.delete({
type: record.Type.JOURNAL_ENTRY,
id: 99999
});
Searching Records: N/search vs N/query
NetSuite offers two ways to search for records in SuiteScript: the traditional N/search module (based on NetSuite Saved Searches) and the newer N/query module (based on SuiteQL, a SQL-like query language). Both are powerful β here’s when to use each.
Use N/search when you need to run an existing Saved Search by its ID, when you need formula fields or summary types, or when you’re working with joined record types using NetSuite’s join syntax. Here’s a basic inline search example:
const searchResults = search.create({
type: search.Type.SALES_ORDER,
filters: [
['status', search.Operator.ANYOF, 'SalesOrd:A'],
'AND',
['total', search.Operator.GREATERTHAN, 1000]
],
columns: ['tranid', 'entity', 'total', 'trandate']
});
searchResults.run().each((result) => {
const orderId = result.getValue('tranid');
const total = result.getValue('total');
log.debug({ title: 'Order', details: `${orderId}: $${total}` });
return true; // return true to continue iterating
});
Use N/query (SuiteQL) when you need SQL-style aggregations (GROUP BY, SUM, COUNT), subqueries, or complex multi-table joins that are difficult to express in the N/search filter syntax:
const results = query.runSuiteQL({
query: `
SELECT c.id, c.companyname, SUM(t.foreigntotal) as totalrevenue
FROM customer c
JOIN transaction t ON t.entity = c.id
WHERE t.recordtype = 'salesorder'
AND t.trandate >= TO_DATE('2026-01-01', 'YYYY-MM-DD')
GROUP BY c.id, c.companyname
ORDER BY totalrevenue DESC
`
});
results.asMappedResults().forEach((row) => {
log.audit({ title: row.companyname, details: `Revenue: $${row.totalrevenue}` });
});
Deploying a SuiteScript: Step-by-Step
Writing the script is only half the work β you also need to upload it to NetSuite and create a Script Record and Script Deployment. Here is the process:
- Upload the script file. Go to Documents β Files β SuiteScripts in your NetSuite account. Upload your
.jsfile to the SuiteScripts folder (or a subfolder of your choice). - Create a Script Record. Go to Customization β Scripting β Scripts β New. Select the script type, choose your uploaded file, and give the script a name and ID (e.g.,
customscript_my_order_alert). - Add the Script Deployment. On the Script Record, go to the Deployments tab and click New Deployment. Select the record type the script applies to (e.g., Sales Order), set the Status to Released, and choose which event triggers to enable.
- Test in a sandbox. Always test your deployment in a NetSuite sandbox account before releasing to production. Use the Execution Log tab on the Script Deployment record to view debug output.
- Set the Status to Released. Once tested, set the deployment status to Released to activate the script in production.
SuiteScript Best Practices
- Always use SuiteScript 2.1. The
@NApiVersion 2.1annotation enables modern JavaScript syntax. There is no reason to write new scripts in 2.0 or 1.0. - Wrap logic in try/catch blocks. Unhandled errors in User Event and Client Scripts can block users from saving records. Always catch exceptions and log them with
log.error(). - Use
record.submitFields()instead of load + save. When you only need to update one or two fields,submitFields()is dramatically cheaper in governance units and faster to execute. - Avoid nested loops with record loads. The classic performance killer: loading a record for each iteration of a loop. Use
N/searchorN/queryto retrieve all the data you need upfront, then process it in memory. - Use script parameters for configurable values. Hard-coding internal IDs (employee IDs, subsidiary IDs, account IDs) makes scripts brittle. Store these as Script Parameters so they can be changed from the Script Deployment UI without editing code.
- Log intelligently in production. Use
log.debug()for development andlog.audit()for key business events. Avoid verbose debug logging in production β execution logs are capped at 500KB per script execution. - Use
context.typeguards in User Event Scripts. MostafterSubmitscripts should only run on CREATE or EDIT, not on every trigger. Always checkcontext.typefirst to avoid unnecessary execution.
SuiteScript vs SuiteFlow: When to Use Each
NetSuite also offers SuiteFlow (Workflow) for automating business logic without code. Many developers ask: when should I use SuiteScript vs SuiteFlow? The answer depends on complexity and maintainability. Use SuiteFlow for straightforward, condition-based automation that business users may need to modify β like sending an approval email when an order reaches a certain status. Use SuiteScript when the logic is complex, involves looping or data transformation, needs to call external APIs, or requires performance-sensitive operations that SuiteFlow cannot handle efficiently.
The two are not mutually exclusive β Workflow Action Scripts let you call custom SuiteScript logic from within a SuiteFlow workflow, giving you the best of both worlds.
Next Steps: Go Deeper with SuiteScript
Now that you understand the fundamentals, here are the next topics to master as a SuiteScript developer:
- How to Create a RESTlet in NetSuite: Step-by-Step Tutorial β Build custom REST APIs with SuiteScript
- Token-Based Authentication (TBA) in NetSuite β Secure your RESTlet and API calls
- Calling a NetSuite RESTlet from External Systems β Connect Postman, Python, and Node.js to your RESTlets
- Magento NetSuite Integration: Complete Developer Guide β Real-world SuiteScript integration example
Discover more from The NetSuite Pro
Subscribe to get the latest posts sent to your email.
Leave a Reply