Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • Advanced PDF Templates in NetSuite
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • Advanced PDF Templates in NetSuite
  • Blog
  • Contact Us
Home/ NetSuite Scripting/Suitelets with Dynamic Forms (SuiteScript 2.1)

Suitelets with Dynamic Forms (SuiteScript 2.1)

🔹 What is a Suitelet?

A Suitelet is a server-side script that renders custom UI inside NetSuite: forms, lists, tabs, field groups, buttons—even mini apps. You control:

  • What fields appear (and when)
  • How options are populated (dynamic data from searches/APIs)
  • How submissions are processed (save to records, call integrations)

Typical flow:

  • GET request → render form
  • POST request → read submitted values, do work (save records, call APIs), then show a success page or re-render the form

🔹 Key APIs you’ll use

  • N/ui/serverWidget → build forms, fields, sublists, tabs, help
  • N/record, N/search → load/save data, populate options
  • N/runtime, N/url → parameters, self-redirect for dynamic filtering
  • (optional) form.clientScriptModulePath → attach a Client Script for richer UX

🔹 Example 1 — Minimal Suitelet (GET/POST) with Dynamic Select

Goal:

  • GET: Render a form with a dynamic select of active customers (loaded via search).
  • POST: Read values and show a confirmation message.
/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 */
define(['N/ui/serverWidget', 'N/search'], (serverWidget, search) => {
  const onRequest = (context) => {
    try {
      if (context.request.method === 'GET') {
        // 1) Build the form
        const form = serverWidget.createForm({
          title: 'Demo: Suitelet Dynamic Form'
        });

        // 2) Add a field group to keep things tidy
        form.addFieldGroup({
          id: 'grp_main',
          label: 'Selection'
        });

        // 3) Add a dynamic Select field for Customer
        const fldCustomer = form.addField({
          id: 'custpage_customer',
          type: serverWidget.FieldType.SELECT,
          label: 'Customer',
          container: 'grp_main'
        });
        fldCustomer.isMandatory = true;

        // 4) Populate select options from a search (active customers)
        fldCustomer.addSelectOption({ value: '', text: '-- Select --' });
        const custSearch = search.create({
          type: search.Type.CUSTOMER,
          filters: [['isinactive', 'is', 'F']],
          columns: ['entityid']
        });
        custSearch.run().each((r) => {
          fldCustomer.addSelectOption({
            value: r.id,
            text: r.getValue('entityid')
          });
          return true;
        });

        // 5) Add another text field
        const fldNote = form.addField({
          id: 'custpage_note',
          type: serverWidget.FieldType.TEXT,
          label: 'Note',
          container: 'grp_main'
        });
        fldNote.maxLength = 200;
        fldNote.helpText = 'Optional description stored only for this demo.';

        // 6) Add Submit button
        form.addSubmitButton({ label: 'Submit' });

        // 7) Render the form
        context.response.writePage(form);
        return;
      }

      // ---------------- POST: read submitted values ----------------
      const req = context.request;
      const customerId = req.parameters.custpage_customer;
      const note = req.parameters.custpage_note || '';

      // In a real app, you could now:
      // - create/update a record
      // - call an external API
      // - enqueue a task

      // Show a simple confirmation page
      const form = serverWidget.createForm({ title: 'Submitted!' });
      form.addField({
        id: 'custpage_info',
        type: serverWidget.FieldType.INLINEHTML,
        label: 'Info'
      }).defaultValue = `
        <div style="padding:12px 0;">
          <b>Success.</b><br/>
          Customer ID: ${customerId || '(none)'}<br/>
          Note: ${note ? note.replace(/</g,'<') : '(empty)'}
        </div>
      `;
      form.addButton({ id: 'custpage_back', label: 'Go Back', functionName: '' });
      context.response.writePage(form);
    } catch (e) {
      // Basic error page
      const errForm = serverWidget.createForm({ title: 'Error' });
      errForm.addField({
        id: 'custpage_err',
        type: serverWidget.FieldType.INLINEHTML,
        label: 'Error'
      }).defaultValue = `<pre style="color:#b00020">${(e && e.message) || e}</pre>`;
      context.response.writePage(errForm);
    }
  };

  return { onRequest };
});

What’s happening (high level):

  • GET builds the form, adds fields, and populates select options from a search.
  • POST reads request.parameters and shows a simple confirmation page.
  • We use Inline HTML for clean, readable feedback.

🔹 Dynamic Refresh Pattern (server-side filtering)

To change field options based on another field’s value (e.g., subsidiary → customers), you can:

  1. Submit the form to itself (POST), or
  2. Use a redirect to GET with query parameters (often cleaner UX).

Below we’ll show a redirect pattern in Example 2.


🔹 Example 2 — Dependent Dropdown (Subsidiary → Customers) via Self-Redirect

Goal:

  • Choose Subsidiary → re-render the form filtering customers by subsidiary.
  • Keep user inputs when reloading.
/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 */
define(['N/ui/serverWidget', 'N/search', 'N/url'], (serverWidget, search, url) => {
  const onRequest = (context) => {
    try {
      const req = context.request;
      const selSubs = (req.parameters.sel_subsidiary || '').trim();
      const selCustomer = (req.parameters.sel_customer || '').trim();

      if (req.method === 'POST') {
        // When "Filter" is clicked, redirect to GET with query params (to rebuild form server-side)
        const slUrl = url.resolveScript({
          scriptId: context.runtime.getCurrentScript().id,         // same Suitelet
          deploymentId: context.runtime.getCurrentScript().deploymentId,
          returnExternalUrl: false
        });
        const target = `${slUrl}&sel_subsidiary=${encodeURIComponent(selSubs)}&sel_customer=${encodeURIComponent(selCustomer)}`;
        context.response.sendRedirect({ type: url.RedirectType.SUITELET, url: target });
        return;
      }

      // ------------------ GET: Render dynamic form ------------------
      const form = serverWidget.createForm({ title: 'Dependent Dropdowns' });
      form.addFieldGroup({ id: 'grp1', label: 'Filters' });

      // 1) Subsidiary select
      const fldSubs = form.addField({
        id: 'custpage_subs',
        type: serverWidget.FieldType.SELECT,
        label: 'Subsidiary',
        container: 'grp1'
      });
      fldSubs.addSelectOption({ value: '', text: '-- All --' });

      // Populate subsidiaries
      search.create({
        type: 'subsidiary',
        filters: [['isinactive', 'is', 'F']],
        columns: ['name']
      }).run().each(r => {
        fldSubs.addSelectOption({ value: r.id, text: r.getValue('name') });
        return true;
      });
      if (selSubs) fldSubs.defaultValue = selSubs;

      // 2) Customer select filtered by subsidiary (if chosen)
      const fldCust = form.addField({
        id: 'custpage_customer',
        type: serverWidget.FieldType.SELECT,
        label: 'Customer',
        container: 'grp1'
      });
      fldCust.addSelectOption({ value: '', text: '-- Select --' });

      const filters = [['isinactive', 'is', 'F']];
      if (selSubs) filters.push('AND', ['subsidiary', 'anyof', selSubs]);

      const custSearch = search.create({
        type: search.Type.CUSTOMER,
        filters,
        columns: ['entityid']
      });
      custSearch.run().each(r => {
        fldCust.addSelectOption({ value: r.id, text: r.getValue('entityid') });
        return true;
      });
      if (selCustomer) fldCust.defaultValue = selCustomer;

      // 3) Buttons: Filter (POST) and Submit (final submit)
      form.addSubmitButton({ label: 'Filter' }); // posts & redirects back with params
      form.addButton({ id: 'custpage_submit', label: 'Submit Selection', functionName: '' });

      // (In a fuller example, you’d handle Submit Selection with POST business logic)

      context.response.writePage(form);
    } catch (e) {
      const errForm = serverWidget.createForm({ title: 'Error' });
      errForm.addField({
        id: 'custpage_err',
        type: serverWidget.FieldType.INLINEHTML,
        label: 'Error'
      }).defaultValue = `<pre style="color:#b00020">${(e && e.message) || e}</pre>`;
      context.response.writePage(errForm);
    }
  };
  return { onRequest };
});

Why this pattern?

  • The server rebuilds filtered options consistently (no client-side logic required).
  • Works well even without a Client Script.
  • Maintains state via query params.

🔹 Example 3 — Dynamic Sublist Input + Save to a Custom Record

Goal:

  • GET: Render a form with a static body fields + editable sublist for line items.
  • POST: Read submitted sublist values and create child custom records.

Assumptions:

  • You have a custom record type: customrecord_demo_parent
  • And a child type: customrecord_demo_child with fields custrecord_demo_parent_ref, custrecord_demo_line_text, custrecord_demo_line_qty
/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 */
define(['N/ui/serverWidget', 'N/record'], (serverWidget, record) => {
  const onRequest = (context) => {
    try {
      if (context.request.method === 'GET') {
        // ---------- Build form ----------
        const form = serverWidget.createForm({ title: 'Dynamic Sublist Entry' });
        form.addFieldGroup({ id: 'grp_head', label: 'Header' });

        const fldTitle = form.addField({
          id: 'custpage_title',
          type: serverWidget.FieldType.TEXT,
          label: 'Title',
          container: 'grp_head'
        });
        fldTitle.isMandatory = true;

        // Create an editable sublist
        const sub = form.addSublist({
          id: 'custpage_lines',
          label: 'Lines',
          type: serverWidget.SublistType.INLINEEDITOR
        });

        // Add columns (fields) to the sublist
        sub.addField({
          id: 'custpage_line_text',
          type: serverWidget.FieldType.TEXT,
          label: 'Description'
        }).isMandatory = true;

        sub.addField({
          id: 'custpage_line_qty',
          type: serverWidget.FieldType.INTEGER,
          label: 'Qty'
        }).isMandatory = true;

        // (Optionally pre-seed a few empty lines for UX)
        // Not necessary with INLINEEDITOR; user can add lines directly.

        form.addSubmitButton({ label: 'Save' });
        context.response.writePage(form);
        return;
      }

      // ---------- POST: Save data ----------
      const req = context.request;
      const title = req.parameters.custpage_title;

      // 1) Create parent custom record
      const parentId = record.create({
        type: 'customrecord_demo_parent',
        isDynamic: true
      }).setValue({ fieldId: 'name', value: title }).save();

      // 2) Read sublist line count from submitted form
      const lineCount = req.getLineCount({ group: 'custpage_lines' });

      for (let i = 0; i < lineCount; i++) {
        try {
          // 3) Read each line’s values
          const lineText = req.getSublistValue({
            group: 'custpage_lines',
            name: 'custpage_line_text',
            line: i
          });
          const lineQty = parseInt(req.getSublistValue({
            group: 'custpage_lines',
            name: 'custpage_line_qty',
            line: i
          }) || '0', 10);

          if (!lineText || !lineQty) continue;

          // 4) Create child record for each line
          const child = record.create({
            type: 'customrecord_demo_child',
            isDynamic: true
          });
          child.setValue({ fieldId: 'custrecord_demo_parent_ref', value: parentId });
          child.setValue({ fieldId: 'custrecord_demo_line_text', value: lineText });
          child.setValue({ fieldId: 'custrecord_demo_line_qty', value: lineQty });
          child.save();
        } catch (lineErr) {
          log.error('Line Save Error', lineErr.message);
        }
      }

      // 5) Confirmation page
      const form = serverWidget.createForm({ title: 'Saved' });
      form.addField({
        id: 'custpage_msg',
        type: serverWidget.FieldType.INLINEHTML,
        label: 'Msg'
      }).defaultValue = `<div style="padding:12px 0;">Saved parent #${parentId} and ${lineCount} line(s).</div>`;
      context.response.writePage(form);

    } catch (e) {
      const errForm = serverWidget.createForm({ title: 'Error' });
      errForm.addField({
        id: 'custpage_err',
        type: serverWidget.FieldType.INLINEHTML,
        label: 'Error'
      }).defaultValue = `<pre style="color:#b00020">${(e && e.message) || e}</pre>`;
      context.response.writePage(errForm);
    }
  };

  return { onRequest };
});

What to notice:

  • Sublist type INLINEEDITOR = user can add lines directly on the Suitelet page.
  • On POST, use request.getLineCount() and request.getSublistValue() to read submitted lines.
  • Save parent first, then child lines referencing the parent.

🔹 Making it feel “rich”: attach a Client Script

  • Set form.clientScriptModulePath = 'SuiteScripts/your_cs_module.js'
  • Handle field dependencies live (no redirect), validate rows before submit, add keyboard shortcuts, etc.

🔹 Best Practices

  • GET fast, POST smart: keep rendering light; do heavy work on POST (or queue tasks).
  • Validate inputs on POST (and client-side if you attach a Client Script).
  • Escape user input when echoing back (avoid HTML injection).
  • Use Field Groups / Tabs to organize complex pages.
  • For very large lists, consider Sublist = LIST (read-only) plus paging, or build to CSV/Excel download.
  • Log errors with enough context (user, params, line index).

âś… Key Takeaway

Suitelets let you build custom mini-apps inside NetSuite. With dynamic fields, dependent selects, editable sublists, and clean GET/POST handling, you can create powerful data entry and review tools tailored to your workflows.

Share
  • Facebook

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 2
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 21 Points
Begginer
admin

admin

  • 5 Questions
  • 2 Points

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

© 2025 The NetSuite Pro. All Rights Reserved