# SuiteScript 2.1 Scheduled Script Generator for NetSuite

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

## Short answer

Generate a restartable scheduled script that processes a saved search in internal ID order, checks remaining usage and resubmits itself before hitting the 10,000-unit limit.

## Category

developer

## Erp

NetSuite

## Audience

- SuiteScript developer
- NetSuite administrator

## Tool

**Kind:** template

**Output:** code

**Language:** javascript

**Fields:**

#### Script name (comment)

**Key:** script_name

**Type:** text

**Default:** Mark processed sales orders

#### Saved search ID

**Key:** saved_search_id

**Type:** text

**Default:** customsearch_open_so_to_close

**Help:** Script ID or internal ID. Use Main Line = true so each transaction appears once.

#### Action per result

**Key:** action

**Type:** select

**Options:**

- submitFields
- closeLines
- logOnly

**Default:** submitFields

**Help:** submitFields sets a checkbox, closeLines closes every item line, logOnly is a dry run.

#### Checkbox field to set (submitFields)

**Key:** field_id

**Type:** text

**Default:** custbody_processed

#### Stop when remaining units fall below

**Key:** units_floor

**Type:** number

**Default:** 200

**Help:** Must cover the most expensive single iteration plus the reschedule call.

#### Script parameter for last internal ID

**Key:** param_id

**Type:** text

**Default:** custscript_last_id

**Help:** Create it on the script record as an Integer Number parameter.

#### Reschedule when the floor is reached

**Key:** reschedule

**Type:** select

**Options:**

- Yes
- No

**Default:** Yes

**Blocks:**

#### Scheduled script (upload to the File Cabinet, create a Script record)

**Body:** /**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 * @NModuleScope SameAccount
 */
// {{script_name}}
define(['N/search', 'N/record', 'N/runtime', 'N/task', 'N/log'], (search, record, runtime, task, log) => {
  const SEARCH_ID = '{{saved_search_id}}';
  const ACTION = '{{action}}'; // submitFields | closeLines | logOnly
  const FIELD_ID = '{{field_id}}';
  const UNITS_FLOOR = {{units_floor}};
  const PARAM_ID = '{{param_id}}';
  const RESCHEDULE = '{{reschedule}}' === 'Yes';
  const BATCH = 1000;

  const processResult = (result) => {
    if (ACTION === 'submitFields') {
      record.submitFields({
        type: result.recordType,
        id: result.id,
        values: { [FIELD_ID]: true },
        options: { enableSourcing: false, ignoreMandatoryFields: true }
      });
    } else if (ACTION === 'closeLines') {
      const rec = record.load({ type: result.recordType, id: result.id });
      const lineCount = rec.getLineCount({ sublistId: 'item' });
      for (let i = 0; i < lineCount; i++) {
        rec.setSublistValue({ sublistId: 'item', fieldId: 'isclosed', line: i, value: true });
      }
      rec.save({ ignoreMandatoryFields: true });
    } else {
      log.debug({ title: 'Would process', details: result.recordType + ' ' + result.id });
    }
  };

  const reschedule = (lastId) => {
    const scheduledTask = task.create({
      taskType: task.TaskType.SCHEDULED_SCRIPT,
      scriptId: runtime.getCurrentScript().id,
      params: { [PARAM_ID]: lastId }
    });
    const taskId = scheduledTask.submit();
    log.audit({ title: 'Rescheduled', details: 'Task ' + taskId + ', resume after internal id ' + lastId });
  };

  const execute = (context) => {
    const script = runtime.getCurrentScript();
    let lastId = Number(script.getParameter({ name: PARAM_ID })) || 0;
    log.audit({ title: 'Start', details: 'type=' + context.type + ', after internal id ' + lastId });

    const savedSearch = search.load({ id: SEARCH_ID });
    const baseFilters = savedSearch.filters.slice();
    savedSearch.columns = [search.createColumn({ name: 'internalid', sort: search.Sort.ASC })];
    let processed = 0;

    while (true) {
      savedSearch.filters = baseFilters.concat([
        search.createFilter({ name: 'internalidnumber', operator: search.Operator.GREATERTHAN, values: [lastId] })
      ]);
      const results = savedSearch.run().getRange({ start: 0, end: BATCH });
      if (results.length === 0) break;

      for (const result of results) {
        if (script.getRemainingUsage() < UNITS_FLOOR) {
          log.audit({ title: 'Usage floor reached', details: processed + ' processed, last id ' + lastId });
          if (RESCHEDULE) reschedule(lastId);
          return;
        }
        try {
          processResult(result);
        } catch (e) {
          log.error({ title: 'Failed ' + result.recordType + ' ' + result.id, details: e.name + ': ' + e.message });
        }
        lastId = Number(result.id);
        processed++;
      }
      if (results.length < BATCH) break;
    }
    log.audit({ title: 'Done', details: processed + ' records processed, remaining units ' + script.getRemainingUsage() });
  };

  return { execute };
});

**Notes:** Verify costs against the governance table for your release: scheduled scripts get 10,000 units; search.load 5, ResultSet.getRange 10, record.submitFields 10 for transactions (5 for other standard records, 2 for custom records), record.load 10 and Record.save 20 for transactions, ScheduledScriptTask.submit 20. The reschedule call omits deploymentId so NetSuite picks an available deployment; keep a spare deployment with status Not Scheduled so one is free while the current run is still executing.

## Content

**Intro:** This generator writes a SuiteScript 2.1 scheduled script that works through a saved search in internal ID order, applies one action per record, tracks remaining governance and resubmits itself with the last processed ID. It is for developers who need a batch job that survives the 10,000-unit limit without skipping or repeating records.

**Sections:**

#### How to use it

**Body:** Build the saved search first, with the criteria that select records to process and Main Line set to true for transaction searches. Choose an action, set the field and parameter IDs, and copy the code into a .js file. Upload it to the File Cabinet (SuiteScripts folder), create a Script record from it, and add an Integer Number parameter with the ID you entered. Create a spare deployment with status Not Scheduled so the script can resubmit itself, then run one with Save and Execute, or schedule it. Start with logOnly to confirm the record count before writing anything.

**Bullets:**

- The deploying role needs the SuiteScript permission plus edit rights on the target records.
- Execution logs are on the deployment record and in Script Execution Logs.
- Set log level to Audit in production to keep logs readable.

#### How the restart logic works

**Body:** The script replaces the saved search columns with internal ID sorted ascending and adds a filter internalidnumber greater than the last processed ID. It then reads batches of up to 1000 with getRange from offset 0 every time. Because each batch starts after the last ID rather than at a page offset, records that drop out of the search after being updated (for example when the search excludes custbody_processed = true) cannot shift later pages and cause skips. Before each record it checks runtime.getCurrentScript().getRemainingUsage(); below the floor it submits a new scheduled task with the last ID as parameter and returns. context.type tells you whether the run was SCHEDULED or ON_DEMAND.

#### Worked governance example

**Body:** With submitFields on sales orders, each record costs 10 units, plus 10 per batch of 1000 results and 5 for the initial search.load. A run starting with 10,000 units and a 200-unit floor processes about 978 orders before it reschedules (10,000 minus 5, minus 10 for the batch, minus the 200 floor, divided by 10). The closeLines action costs 30 per order (load 10, save 20), so about 326 orders per run. Logging is free in units but slow. If you need thousands of records per hour or parallel processing, a map/reduce script is the better fit; a scheduled script runs on one processor at a time.

**Bullets:**

- submitFields on a custom record costs 2 units.
- Keep at least 20 units of floor for the reschedule submit.
- Time limits apply as well as units: very slow saves can end a run early.

#### Common mistakes

**Body:** Paging with runPaged or getRange offsets while the loop changes the records the search returns, which silently skips records. Leaving Main Line off, so each line of a transaction appears as a separate result and the record is updated many times. Hard-coding deploymentId to the running deployment, which fails because it is already executing. Forgetting to create the script parameter, so the restart point is always zero. Setting the floor too low for closeLines, where one iteration costs 30 units. Not wrapping each record in try/catch, so one locked or invalid record stops the whole run.

**Cta:** If you want an assistant that finds the records needing attention and drafts the batch job for review on your own infrastructure, we can build that with you.

**Faq:**

#### How do I load a saved search in SuiteScript 2.1?

**Answer:** Use search.load({ id: 'customsearch_id' }), which costs 5 units, then run it with search.run().getRange({ start, end }) for up to 1000 results at a time, or runPaged({ pageSize }) and fetch each page. You can add filters with search.createFilter and replace columns before running.

#### How many governance units does a scheduled script have?

**Answer:** A scheduled script gets 10,000 units per execution. Check what is left with runtime.getCurrentScript().getRemainingUsage() and resubmit the script with task.create of type SCHEDULED_SCRIPT before you run out, passing the restart point as a script parameter. Submitting the task costs 20 units.

#### What permission is needed to schedule a SuiteScript?

**Answer:** The person creating the script record and deployment needs the SuiteScript permission, typically on a developer or administrator role. The deployment's Execute As Role decides what the script can read and change, and the records touched need matching permissions. Scheduling is set on the deployment's Schedule subtab.

#### When should I use map/reduce instead of a scheduled script?

**Answer:** Use map/reduce when the volume is large, the work per record is independent, and you want NetSuite to handle governance yielding and parallel processing. Keep a scheduled script for ordered, sequential work, small daily jobs, or when you need simple control over the restart point, as in this generator.

## Search terms

netsuite suitescript saved search, netsuite suitescript load saved search, netsuite suitescript get search results, netsuite permission suitescript scheduling, netsuite suitescript tutorial, suitescript 2.1 scheduled script example, suitescript reschedule scheduled script, suitescript getremainingusage

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