SuiteScript Map/Reduce Skeleton Generator
Map/reduce is the SuiteScript type for bulk work, because NetSuite splits input into many small invocations and handles queueing and retries. This generator creates a working skeleton with an input query, map, reduce and a summarize stage that reports errors.
Fill in
map_reduce_skeleton.js
/**
* @NApiVersion 2.1
* @NScriptType MapReduceScript
* @name Close Old Sales Orders
* @author Your Name
* @version 1.0 (2026-10-20)
*/
define(['N/record', 'N/query', 'N/log'], (record, query, log) => {
const getInputData = () => {
const sql = "SELECT id FROM transaction WHERE type = 'SalesOrd' AND status = 'B' AND trandate < TO_DATE('2025-01-01', 'YYYY-MM-DD')";
return query.runSuiteQL({ query: sql }).asMappedResults();
};
const map = (context) => {
try {
const row = JSON.parse(context.value);
// Emit by key. Rows with the same key reach the same reduce call.
context.write({ key: String(row.id), value: row.id });
} catch (e) {
log.error({ title: 'map failed', details: e });
throw e;
}
};
const reduce = (context) => {
try {
const id = context.key;
record.submitFields({
type: record.Type.SALES_ORDER,
id: id,
values: { memo: 'Processed by Close Old Sales Orders' },
options: { enableSourcing: false, ignoreMandatoryFields: true }
});
context.write({ key: id, value: 'ok' });
} catch (e) {
log.error({ title: 'reduce failed for ' + context.key, details: e });
throw e;
}
};
const summarize = (summary) => {
let processed = 0;
let failed = 0;
summary.output.iterator().each((key, value) => { processed += 1; return true; });
summary.mapSummary.errors.iterator().each((key, error) => {
failed += 1;
log.error({ title: 'Map error ' + key, details: error });
return true;
});
summary.reduceSummary.errors.iterator().each((key, error) => {
failed += 1;
log.error({ title: 'Reduce error ' + key, details: error });
return true;
});
if (summary.inputSummary.error) {
log.error({ title: 'Input error', details: summary.inputSummary.error });
}
log.audit({ title: 'Done', details: 'processed=' + processed + ' failed=' + failed + ' usage=' + summary.usage });
};
return { getInputData, map, reduce, summarize };
});Deployment settings to check
Concurrency limit: start low (for example 2 to 5) and raise it after testing. Yield after minutes: leave the default unless you see long-running map calls. Buffer size: leave the default. Status: Testing with a narrow input query until verified.
The example updates the memo of matching records, which is a harmless placeholder. Replace the reduce logic with your real change and test it on a handful of records. Verify the input query returns the intended rows before enabling a wide deployment.
Batch job design review
Optional review of a NetSuite bulk update design, concurrency and error handling.
Optional. The tool and every export work without this. No spam.
How to use the generator
Set a script name, author, record type and the SuiteQL query that selects the rows to process. Copy the generated file to the File Cabinet, create the script record and a deployment, and run it against a small input first. Replace the reduce body with your actual update. The summarize stage logs every map and reduce error with its key, which is what you will read after a run.
How the four stages work
getInputData returns the list of work, from a query, a search or an array. map receives one input row at a time and writes key and value pairs. reduce receives all values for one key, which lets you group related rows into one unit of work, such as all lines for one order. summarize runs once at the end with counts, usage and every error collected from earlier stages.
- Each map and reduce call has its own governance allowance
- Values passed between stages must be JSON serializable
- Rows are processed in parallel, so do not rely on order
- Throwing in map or reduce records the error and continues
Design guidance
Choose the key in map carefully, since it decides how work groups in reduce. Use the record ID when each row is independent, and a parent ID when child rows must be updated together. Keep concurrency modest to avoid record locking and to stay inside the account concurrency limit shared with integrations. Prefer submitFields for small changes, because it costs far fewer units than loading and saving a transaction.
Common mistakes
Common issues are returning huge objects from getInputData that exceed storage limits, writing values that are not serializable, updating the same record from several reduce calls at once and hitting a lock error, and ignoring summarize so failures go unnoticed. Always test with a small input, check the Map/Reduce Script Status page, and confirm results with a saved search or SuiteQL query.
Frequently Asked Questions
When should I use map/reduce instead of a scheduled script?
Use map/reduce when you process thousands of records, need parallelism or want automatic retry handling. A scheduled script is simpler for small, sequential jobs, but it has one governance allowance for the entire run.
What does the key in map control?
All values written with the same key are delivered to a single reduce call. Choose a key that groups the data that must be handled together, such as a parent record ID.
How do I find errors after a run?
Read the summarize stage logs, which iterate over map and reduce errors, and check Customization > Scripting > Map/Reduce Script Status for stage progress and failures.
Can getInputData use SuiteQL?
Yes. Return query.runSuiteQL(...).asMappedResults() or a paged query, and keep the result size reasonable. You can also return a saved search or an array of objects.
If you run large NetSuite batch jobs, Netray can help design them and add read-only AI checks over the results, with approval on any write-back.
Related Tools
NetSuite 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.
NetSuiteSuiteQL 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 2.1 User Event Script Skeleton
A copy-ready SuiteScript 2.1 user event script with the JSDoc annotations, entry points, context checks and error logging you need to start.
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
New to NetSuite? Start with the NetSuite guide and toolkit, or plan with NetSuite Cost Calculator, Training Path Builder, ERP Certification Study Planner.
Keep exploring
Free tools
- SuiteScript 2.1 RESTlet Skeleton GeneratorFill in a few fields and get a SuiteScript 2.1 RESTlet starter with all four handlers, logging and structured error responses.
- NetSuite SuiteScript 2.1 RESTlet Skeleton GeneratorFill in a script name, record type and fields to get a SuiteScript 2.1 RESTlet with GET lookup, POST create, logging and error handling.
- NetSuite Map/Reduce Parallelism Throughput CalculatorEstimate map stage runtime, speed-up and efficiency from the processor count you configure and the share you actually receive.
ERP fixes and how-tos
- Avoiding governance limit errors in NetSuite Map/Reduce scriptsUnderstand how governance is allocated per key in NetSuite Map/Reduce scripts, why SSS_USAGE_LIMIT_EXCEEDED still happens, and how to design getInputData, map, reduce and summarize stages that do not time out.
- Fix NetSuite SSS_USAGE_LIMIT_EXCEEDED ErrorSSS_USAGE_LIMIT_EXCEEDED means your SuiteScript ran out of governance units. Learn the cause, how to check usage, and fixes using submitFields and Map/Reduce.
- Fix NetSuite SSS_MISSING_REQD_ARGUMENT ErrorSSS_MISSING_REQD_ARGUMENT means a NetSuite API call is missing a required parameter like type or id. Learn the common causes and how to fix it fast.
Integrations
- How to add WhatsApp to Oracle NetSuiteHow to connect WhatsApp Business to Oracle NetSuite: SuiteScript triggers, RESTlets, template messages, opt-in rules and two-way replies that post back to records.
- How to connect Slack to Oracle NetSuiteAdd Slack to Oracle NetSuite: build a Slack app with slash commands, interactive approvals and alerts using SuiteScript, RESTlets and SuiteQL, with security notes.