# NetSuite SuiteScript 2.1 RESTlet Skeleton Generator

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

## Short answer

Fill in a script name, record type and fields to get a SuiteScript 2.1 RESTlet with GET lookup, POST create, logging and error handling.

## Category

developer

## Erp

NetSuite

## Audience

- NetSuite developer
- Integration engineer
- NetSuite administrator

## Tool

**Kind:** template

**Output:** code

**Language:** javascript

**Fields:**

#### Script description

**Key:** script_name

**Type:** text

**Default:** Work order status lookup

#### Author

**Key:** author

**Type:** text

**Default:** Integration Team

#### record.Type constant

**Key:** record_enum

**Type:** select

**Options:**

- WORK_ORDER
- ASSEMBLY_BUILD
- SALES_ORDER
- PURCHASE_ORDER
- INVENTORY_ADJUSTMENT
- ITEM_RECEIPT

**Default:** WORK_ORDER

#### Record label for messages

**Key:** record_label

**Type:** text

**Default:** work order

#### First field id to return

**Key:** field_one

**Type:** text

**Default:** tranid

#### Second field id to return

**Key:** field_two

**Type:** text

**Default:** trandate

#### Script date

**Key:** script_date

**Type:** date

**Default:** 2026-10-20

**Blocks:**

#### RESTlet script file

**Body:** /**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 * @NModuleScope SameAccount
 * @description {{script_name}}
 * @author {{author}}
 * @since {{script_date}}
 */
define(['N/record', 'N/log', 'N/error'], (record, log, error) => {

  const get = (requestParams) => {
    try {
      if (!requestParams || !requestParams.id) {
        throw error.create({ name: 'MISSING_ID', message: 'Query parameter id is required', notifyOff: true });
      }
      const rec = record.load({ type: record.Type.{{record_enum}}, id: requestParams.id, isDynamic: false });
      return {
        success: true,
        id: requestParams.id,
        {{field_one}}: rec.getValue({ fieldId: '{{field_one}}' }),
        {{field_two}}: rec.getValue({ fieldId: '{{field_two}}' })
      };
    } catch (e) {
      log.error({ title: 'GET {{record_label}} failed', details: e });
      return { success: false, name: e.name, message: e.message };
    }
  };

  const post = (requestBody) => {
    try {
      if (!requestBody || !requestBody.values) {
        throw error.create({ name: 'MISSING_BODY', message: 'Body must contain a values object', notifyOff: true });
      }
      const rec = record.create({ type: record.Type.{{record_enum}}, isDynamic: true });
      Object.keys(requestBody.values).forEach((fieldId) => {
        rec.setValue({ fieldId: fieldId, value: requestBody.values[fieldId] });
      });
      const newId = rec.save({ enableSourcing: true, ignoreMandatoryFields: false });
      log.audit({ title: 'Created {{record_label}}', details: newId });
      return { success: true, id: newId };
    } catch (e) {
      log.error({ title: 'POST {{record_label}} failed', details: e });
      return { success: false, name: e.name, message: e.message };
    }
  };

  return { get, post };
});

#### Deployment checklist as comments

**Body:** // 1. Upload the file to the File Cabinet, for example SuiteScripts/restlets/
// 2. Customization > Scripting > Scripts > New, choose the file, create the script record
// 3. Deploy it, set status to Testing, restrict Audience to the integration role
// 4. Call the External URL from the deployment record with OAuth 2.0 or token based authentication
// 5. Move status to Released only after a sandbox test with the same role

**Notes:** This is a skeleton, not production code. Validate the field ids against your record, add input validation, and check governance usage: a RESTlet gets 5,000 units per call. Many real integrations return a standard HTTP error instead of success false, so adapt the error branch to your client.

## Content

**Intro:** A RESTlet is the quickest way to give an external system a custom endpoint into NetSuite. The structure is always the same: an annotated define block, a get and a post entry point, and careful error handling. This generator writes that structure for the record type you choose.

**Sections:**

#### How to use the skeleton

**Body:** Select the record.Type constant, name the two fields you want returned, and copy the script. Save it as a .js file in the File Cabinet, create the script record and deploy it. The generated GET handler loads one record by id. The POST handler creates a record from a values object, which is simple enough to test but not enough for complex transactions with sublists.

**Bullets:**

- Replace the field ids with fields that exist on your record
- Add sublist handling with selectNewLine and commitLine where needed
- Keep the deployment on Testing until a sandbox call succeeds
- Limit the audience to the integration role, not all roles

#### Why 2.1 and these modules

**Body:** SuiteScript 2.1 supports modern JavaScript syntax such as arrow functions, const and let, which is why the skeleton uses them. N/record handles loading and saving, N/log writes to the execution log, and N/error creates named errors that are easier to filter. Use N/search or N/query when you need to read many records instead of loading each one, because record.load consumes far more governance than a search result.

#### Authentication and permissions

**Body:** A RESTlet runs with the permissions of the role attached to the calling token or OAuth client. If the role cannot edit a work order, the POST fails regardless of what the script does. Create a dedicated integration role with only the permissions the endpoint needs, and never reuse an administrator token for a script that accepts external input.

#### Common mistakes

**Body:** Frequent problems include returning the whole record object, which is not valid JSON, setting values before the form or subsidiary is known so sourcing misfires, and swallowing errors so the caller sees success. Always log the error details and return a message the caller can act on.

**Bullets:**

- Using dynamic mode when standard mode would do
- Not validating the request body before saving
- Hard coding internal ids that differ between sandbox and production
- Deploying to all roles with an open audience

**Cta:** If you are building integrations between NetSuite and shop floor or engineering systems, we can review the design and the governance risks with you before you deploy.

**Faq:**

#### What is the governance limit for a RESTlet?

**Answer:** A RESTlet script gets 5,000 governance units per invocation. A record.load or record.save on a transaction costs a noticeable share, so batch work through searches and keep each request small. The execution log shows remaining units when you query it.

#### Should I use a RESTlet or the REST record API?

**Answer:** Use the standard REST record API when the default record shape is enough, because there is no code to maintain. Use a RESTlet when you need custom logic, a combined response from several records, or a simpler payload than the standard API provides.

#### Can I use SuiteScript 2.0 instead?

**Answer:** Yes, 2.0 still runs, but 2.1 is the current version and supports modern syntax. Existing 2.0 scripts do not need to be rewritten, though new scripts are usually written in 2.1 to avoid a later migration.

#### Is the generated code safe to deploy as is?

**Answer:** No. It is a starting point. Add validation, permission thinking, retries on the caller side and tests in a sandbox. Review field ids and mandatory fields for your own forms and customizations before releasing.

## Search terms

suitescript 2.1 restlet example, netsuite restlet template, netsuite restlet get post sample, suitescript restlet skeleton, netsuite restlet record.load, netsuite restlet error handling, netsuite suitescript generator, netsuite restlet json

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