Guide · Updated October 2026
SuiteScript 2.1 migration: what changes, who is affected, and how to get there before 2028.2
Oracle has set a date: by the NetSuite 2028.2 release, custom scripts must run on SuiteScript 2.1. That covers the obvious SuiteScript 1.0 code and, less obviously, every 2.0 script in the account. This guide walks through the timeline, the technical differences, and a plan that starts from what is actually deployed.
The timeline
| Release | What changes | What you should have done |
|---|---|---|
| 2026.2 | SuiteScript 2.1 is the standard version for new and existing customizations. | Write new scripts in 2.1. Start the inventory of what already runs. |
| 2027.1 | SuiteScript 1.0 enters end-of-life support: critical issues only. | Have every active 1.0 script converted or retired. |
| 2028.1 | SuiteScript 2.0 and 2.x server scripts run under the 2.1 engine by default. | Finish testing 2.0 scripts in the 2.1 runtime before this release. |
| 2028.2 | Custom scripts must use SuiteScript 2.1. Scripts on 1.0, 2.0 or 2.x stop working. | Nothing legacy left in Production. |
Source: Oracle NetSuite SuiteAnswers article 1047412, “SuiteScript 2.1 Required for All Custom Scripts in NetSuite 2028.2”. Release dates for 2028 are not yet published; plan against the release number.
Which scripts are affected
Three groups, with very different amounts of work:
- SuiteScript 1.0 (nlapi* functions, no define() block). These need a rewrite into the 2.1 module structure. End-of-life support begins in 2027.1, a full year before the hard stop.
- SuiteScript 2.0 and 2.x (define() with N/ modules, @NApiVersion 2.0 or 2.x). These need testing under the 2.1 engine and an annotation change. Most pass; some break on strict mode.
- Scripts you cannot read: locked bundles, vendor SuiteApps, restricted folders. These are not yours to convert. Each one is a vendor conversation and a line in the risk register.
Client scripts, user events, Suitelets, RESTlets, scheduled scripts, Map/Reduce, portlets, mass updates and workflow actions are all in scope. Only active deployments matter; inactive scripts can be retired.
2.0 to 2.1: why “just change the annotation” is not enough
SuiteScript 2.0 and 2.1 share the same API surface, so the migration looks trivial. The difference is the engine underneath. 2.1 runs on GraalJS and applies rules that Rhino-era 2.0 code was never held to:
| What breaks | Why, and the fix |
|---|---|
| Strict mode is enforced | Assigning to an undeclared variable throws instead of creating a global. Declare everything with const or let. |
| `this` is undefined in plain functions | Functions called without a receiver see `this` as undefined, not the global object. Code that relied on implicit globals through `this` fails. |
| Shared global state is gone | Variables you leaked onto the global object in one function are not visible from another. Pass values explicitly or keep them in module scope. |
| Different JavaScript engine | 2.1 runs on GraalJS and supports modern syntax (classes, arrow functions, template literals, Promise, async/await on supported script types). Rhino-specific quirks and Java interop no longer work. |
| Date and number formatting | Locale-dependent formatting can differ between engines. Format explicitly with N/format rather than toString(). |
NetSuite exposes an account preference that runs 2.0/2.x server scripts under the 2.1 engine. Turn it on in a Sandbox, run the real business flows, and fix what throws. When a script is clean, change its annotation to 2.1 and move on.
1.0 to 2.1: what a rewrite involves
A 1.0 script is a set of global functions calling nlapi* helpers. A 2.1 script is a module: a JSDoc header, a define() call that loads the N/ modules it needs, entry points that take a context object, and a returned object naming those entry points. The same user event, before and after:
// SuiteScript 1.0 — User Event
function beforeSubmit(type) {
var rec = nlapiGetNewRecord();
var limit = nlapiLookupField('customer',
rec.getFieldValue('entity'), 'creditlimit');
if (parseFloat(rec.getFieldValue('total')) > parseFloat(limit)) {
rec.setFieldValue('custbody_credit_hold', 'T');
nlapiLogExecution('AUDIT', 'Credit hold', rec.getId());
}
}/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/search', 'N/log'], (search, log) => {
const beforeSubmit = (context) => {
const rec = context.newRecord;
const { creditlimit } = search.lookupFields({
type: 'customer',
id: rec.getValue({ fieldId: 'entity' }),
columns: ['creditlimit'],
});
if (parseFloat(rec.getValue({ fieldId: 'total' })) > parseFloat(creditlimit)) {
rec.setValue({ fieldId: 'custbody_credit_hold', value: true });
log.audit({ title: 'Credit hold', details: rec.id });
}
};
return { beforeSubmit };
});Beyond the structure, every nlapi* call maps to a module method. The most common ones:
| SuiteScript 1.0 | SuiteScript 2.1 | Module |
|---|---|---|
| nlapiLoadRecord(type, id) | record.load({ type, id }) | N/record |
| nlapiCreateRecord(type) | record.create({ type }) | N/record |
| nlapiSubmitRecord(rec) | rec.save() | N/record |
| nlapiSubmitField(type, id, field, value) | record.submitFields({ type, id, values }) | N/record |
| nlapiLookupField(type, id, fields) | search.lookupFields({ type, id, columns }) | N/search |
| nlapiSearchRecord(type, id, filters, columns) | search.create({ type, filters, columns }).run() | N/search |
| nlapiGetFieldValue(field) | currentRecord.getValue({ fieldId }) | N/currentRecord |
| nlapiSetFieldValue(field, value) | currentRecord.setValue({ fieldId, value }) | N/currentRecord |
| nlapiLogExecution(level, title, details) | log.debug / log.audit / log.error({ title, details }) | N/log |
| nlapiRequestURL(url, body, headers) | https.get / https.post({ url, body, headers }) | N/https |
| nlapiSendEmail(author, recipient, subject, body) | email.send({ author, recipients, subject, body }) | N/email |
| nlapiScheduleScript(id, deployId, params) | task.create({ taskType: task.TaskType.SCHEDULED_SCRIPT }).submit() | N/task |
| nlapiGetContext().getRemainingUsage() | runtime.getCurrentScript().getRemainingUsage() | N/runtime |
The mapping is not always one-to-one, and Oracle’s API map lists 1.0 functions with no 2.1 equivalent. Those steps need a redesign rather than a translation. Checkbox fields also change type: 1.0 used the strings ‘T’ and ‘F’; 2.1 uses booleans.
A migration plan that starts from the live account
1. Inventory every active script
List the script records and deployments that are active in Production, with script type and API version. Inactive deployments and scripts with no deployment can usually be retired instead of migrated. Do this from the live account, not from an old SDF project: the account is the source of truth for what actually runs.
2. Classify by version and by source access
Split the list three ways: SuiteScript 1.0 (must be rewritten), 2.0/2.x (must be tested in the 2.1 runtime, then re-annotated) and 2.1 (done). For each legacy script, check whether your NetSuite role can read the source file. Scripts in locked bundles, vendor SuiteApps or restricted folders cannot be converted by you; they need a 2.1-compatible release from the vendor.
3. Prioritise by business impact
Convert 1.0 scripts first; they have the earliest deadline (2027.1 end-of-life support) and need the most work. Within each version, start with scripts on core transactions (sales orders, invoices, item fulfilment) and scheduled or Map/Reduce jobs that run unattended, since failures there surface late.
4. Test 2.0 scripts under the 2.1 runtime in Sandbox
NetSuite provides an account-level preference to run 2.0/2.x server scripts under the 2.1 engine. Turn it on in a Sandbox, exercise the scripts, and fix the breakages listed below. When a script passes, change its @NApiVersion annotation to 2.1.
5. Convert 1.0 scripts to 2.1
Rewrite each script into the 2.1 module structure: a define() block that loads N/ modules, entry points that receive a context object, and a returned object of entry-point functions. Replace every nlapi* call using Oracle's API map. Where no 2.1 equivalent exists, redesign the step; do not emulate the 1.0 behaviour with a shim.
6. Review, test, deploy
Treat every converted file as a draft. Diff it against the original, read the change notes, and run the real business flow in Sandbox with representative data. Deploy to Production in small batches with the ability to re-enable the old script deployment if something regresses.
Tools that help
- Oracle’s SuiteScript upgrade skill. Free and open source. It analyses, converts and validates script files in a local SuiteCloud project. Best when your code already lives in source control.
- SuiteMigrate. A Chrome extension that works from the NetSuite account you are signed in to. It inventories active scripts with their API version, checks which source files your role can read, and converts the scripts you choose into reviewable 2.1 drafts with side-by-side review and change notes. Free for five conversions.
- NetSuite’s own 2.1 runtime preference for testing 2.0 scripts, and the SuiteScript 2.1 debugger in Chrome DevTools for working through failures.
- A NetSuite partner for large estates or when nobody in-house has time. Typical rates run from roughly $120 to $275 per hour, so use the inventory to scope the engagement precisely.
Whatever produces the converted code, the rule is the same: review it, test it in Sandbox, then deploy. No tool is a substitute for that.
Frequently asked questions
Do SuiteScript 2.0 scripts need to be migrated to 2.1?
Yes. From 2028.1, 2.0 and 2.x server scripts run under the 2.1 engine by default, and from 2028.2 custom scripts must use SuiteScript 2.1. Most 2.0 scripts need testing and an annotation change rather than a rewrite, but strict mode and the engine change do break some of them.
What is the difference between SuiteScript 2.0 and 2.1?
Both share the same N/ module API. 2.1 runs on a newer JavaScript engine (GraalJS) that supports modern ECMAScript syntax, enforces strict mode and no longer exposes the Rhino-era global object behaviour that many 2.0 scripts accidentally relied on.
Can I just change @NApiVersion from 2.0 to 2.1?
Only after testing. The annotation switches the runtime engine. Scripts that use undeclared variables, implicit globals or Rhino-specific behaviour will throw at runtime under 2.1, so test in Sandbox first.
Is there a tool that converts SuiteScript 1.0 to 2.1 automatically?
Oracle publishes a free, open-source SuiteScript upgrade skill that converts files in a local SuiteCloud project. SuiteMigrate works from the live NetSuite account instead: it inventories active scripts, shows which source you can read, and produces reviewable 2.1 drafts for the scripts you choose. Every converted script still needs a developer review and Sandbox testing.
What about scripts in bundles or vendor SuiteApps?
If the source is locked or hidden, you cannot convert it yourself and no tool should bypass that protection. Ask the vendor for a SuiteScript 2.1-compatible release and track it as a blocker in your migration plan.
How long does a SuiteScript 2.1 migration take?
It depends on the number of active 1.0 scripts. A 2.0 script typically needs an hour or two of testing; a 1.0 script of a few hundred lines is usually half a day to a day including review and Sandbox testing. Inventory first, so the estimate is based on what actually runs.
Start with the inventory
See which of your scripts still run legacy SuiteScript
SuiteMigrate scans the account you are signed in to and shows every active script with its API version and whether you can read its source. Free, no card, five conversions included.
SuiteMigrate is not affiliated with or endorsed by Oracle Corporation or NetSuite. NetSuite and SuiteScript are trademarks of Oracle and/or its affiliates.