NewNetSuite 2025.2 — What's new
intermediateSuiteScript30 min read

Building Custom Suitelet Forms in NetSuite

Learn to create custom Suitelets with forms, buttons, and sublists. Build interactive NetSuite applications with SuiteScript 2.1.

Prerequisites

  • SuiteScript 2.1 basics
  • Understanding of HTTP GET/POST
  • NetSuite form concepts
SuiteScriptSuiteletCustom FormsNetSuite Development

Suitelets are custom NetSuite pages that allow you to build interactive applications, dashboards, and forms. Unlike record-bound scripts, Suitelets give you complete control over the UI and logic.

What Are Suitelets?

Suitelets are server-side scripts that generate custom pages accessible via URL. They're perfect for:

  • Custom data entry forms
  • Reporting dashboards
  • Integration endpoints
  • Approval workflows
  • Bulk operations interfaces

Basic Suitelet Structure

Every Suitelet needs an onRequest entry point that handles both GET and POST requests:

/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 */
define(['N/ui/serverWidget'], (serverWidget) => {
  const onRequest = (context) => {
    if (context.request.method === 'GET') {
      // Display the form
      const form = serverWidget.createForm({
        title: 'My Custom Form'
      });
 
      // Add fields, buttons, sublists...
 
      context.response.writePage(form);
    } else {
      // Handle form submission (POST)
      const request = context.request;
      const name = request.parameters.custpage_name;
 
      // Process the data...
    }
  };
 
  return { onRequest };
});

Building a Complete Form

Let's build a practical example: a customer feedback form that creates a support case.

/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 * @NModuleScope SameAccount
 *
 * Customer Feedback Form Suitelet
 */
define(['N/ui/serverWidget', 'N/record', 'N/redirect', 'N/log'],
  (serverWidget, record, redirect, log) => {
 
  /**
   * Build the feedback form
   */
  const buildForm = () => {
    const form = serverWidget.createForm({
      title: 'Customer Feedback Form',
      hideNavBar: false
    });
 
    // Add field group for organization
    form.addFieldGroup({
      id: 'custpage_contact_info',
      label: 'Contact Information'
    });
 
    // Customer selection (lookup field)
    const customerField = form.addField({
      id: 'custpage_customer',
      type: serverWidget.FieldType.SELECT,
      label: 'Customer',
      source: 'customer',
      container: 'custpage_contact_info'
    });
    customerField.isMandatory = true;
 
    // Contact name
    form.addField({
      id: 'custpage_contact_name',
      type: serverWidget.FieldType.TEXT,
      label: 'Contact Name',
      container: 'custpage_contact_info'
    }).isMandatory = true;
 
    // Email
    const emailField = form.addField({
      id: 'custpage_email',
      type: serverWidget.FieldType.EMAIL,
      label: 'Email Address',
      container: 'custpage_contact_info'
    });
    emailField.isMandatory = true;
 
    // Feedback section
    form.addFieldGroup({
      id: 'custpage_feedback_info',
      label: 'Feedback Details'
    });
 
    // Category dropdown
    const categoryField = form.addField({
      id: 'custpage_category',
      type: serverWidget.FieldType.SELECT,
      label: 'Category',
      container: 'custpage_feedback_info'
    });
    categoryField.addSelectOption({ value: '', text: '-- Select --' });
    categoryField.addSelectOption({ value: 'product', text: 'Product Issue' });
    categoryField.addSelectOption({ value: 'service', text: 'Service Feedback' });
    categoryField.addSelectOption({ value: 'billing', text: 'Billing Question' });
    categoryField.addSelectOption({ value: 'other', text: 'Other' });
    categoryField.isMandatory = true;
 
    // Priority
    const priorityField = form.addField({
      id: 'custpage_priority',
      type: serverWidget.FieldType.SELECT,
      label: 'Priority',
      container: 'custpage_feedback_info'
    });
    priorityField.addSelectOption({ value: '3', text: 'Low' });
    priorityField.addSelectOption({ value: '2', text: 'Medium' });
    priorityField.addSelectOption({ value: '1', text: 'High' });
    priorityField.defaultValue = '2';
 
    // Description (long text)
    const descField = form.addField({
      id: 'custpage_description',
      type: serverWidget.FieldType.TEXTAREA,
      label: 'Description',
      container: 'custpage_feedback_info'
    });
    descField.isMandatory = true;
    descField.updateDisplaySize({
      height: 10,
      width: 60
    });
 
    // Add submit button
    form.addSubmitButton({
      label: 'Submit Feedback'
    });
 
    // Add reset button
    form.addResetButton({
      label: 'Clear Form'
    });
 
    return form;
  };
 
  /**
   * Process form submission and create support case
   */
  const processSubmission = (request) => {
    const params = request.parameters;
 
    // Create support case record
    const supportCase = record.create({
      type: record.Type.SUPPORT_CASE,
      isDynamic: true
    });
 
    supportCase.setValue({
      fieldId: 'company',
      value: params.custpage_customer
    });
 
    supportCase.setValue({
      fieldId: 'title',
      value: `Feedback: ${params.custpage_category}`
    });
 
    supportCase.setValue({
      fieldId: 'incomingmessage',
      value: params.custpage_description
    });
 
    supportCase.setValue({
      fieldId: 'priority',
      value: params.custpage_priority
    });
 
    supportCase.setValue({
      fieldId: 'email',
      value: params.custpage_email
    });
 
    const caseId = supportCase.save();
 
    log.audit('Feedback Submitted', {
      caseId: caseId,
      customer: params.custpage_customer,
      category: params.custpage_category
    });
 
    return caseId;
  };
 
  /**
   * Show success message
   */
  const showConfirmation = (context, caseId) => {
    const form = serverWidget.createForm({
      title: 'Thank You!'
    });
 
    form.addField({
      id: 'custpage_message',
      type: serverWidget.FieldType.INLINEHTML,
      label: ' '
    }).defaultValue = `
      <div style="padding: 20px; text-align: center;">
        <h2 style="color: #4CAF50;">Feedback Submitted Successfully</h2>
        
Your support case **#${caseId}** has been created.
 
        
Our team will review your feedback and respond within 24 hours.
 
        
 
        <a href="/app/common/custom/custrecordentry.nl?rectype=YOUR_SUITELET_URL"
           style="padding: 10px 20px; background: #607D8B; color: white; text-decoration: none; border-radius: 4px;">
          Submit Another
        </a>
      </div>
    `;
 
    context.response.writePage(form);
  };
 
  /**
   * Main entry point
   */
  const onRequest = (context) => {
    if (context.request.method === 'GET') {
      const form = buildForm();
      context.response.writePage(form);
    } else {
      try {
        const caseId = processSubmission(context.request);
        showConfirmation(context, caseId);
      } catch (e) {
        log.error('Submission Error', e.message);
        // Show error form
        const errorForm = serverWidget.createForm({ title: 'Error' });
        errorForm.addField({
          id: 'custpage_error',
          type: serverWidget.FieldType.INLINEHTML,
          label: ' '
        }).defaultValue = `<p style="color: red;">Error: ${e.message}
`;
        context.response.writePage(errorForm);
      }
    }
  };
 
  return { onRequest };
});

Adding Sublists

Sublists let you display and edit tabular data:

/**
 * Add a sublist to display items
 */
const addItemSublist = (form, items) => {
  const sublist = form.addSublist({
    id: 'custpage_items',
    type: serverWidget.SublistType.LIST,
    label: 'Items'
  });
 
  // Define columns
  sublist.addField({
    id: 'custpage_item_name',
    type: serverWidget.FieldType.TEXT,
    label: 'Item Name'
  });
 
  sublist.addField({
    id: 'custpage_quantity',
    type: serverWidget.FieldType.INTEGER,
    label: 'Quantity'
  });
 
  sublist.addField({
    id: 'custpage_price',
    type: serverWidget.FieldType.CURRENCY,
    label: 'Price'
  });
 
  // Populate data
  items.forEach((item, index) => {
    sublist.setSublistValue({
      id: 'custpage_item_name',
      line: index,
      value: item.name
    });
    sublist.setSublistValue({
      id: 'custpage_quantity',
      line: index,
      value: item.quantity
    });
    sublist.setSublistValue({
      id: 'custpage_price',
      line: index,
      value: item.price
    });
  });
 
  return sublist;
};

Editable Sublist Example

For data entry with multiple rows:

const addEditableSublist = (form) => {
  const sublist = form.addSublist({
    id: 'custpage_line_items',
    type: serverWidget.SublistType.INLINEEDITOR,
    label: 'Line Items'
  });
 
  // Item selection
  sublist.addField({
    id: 'custpage_item',
    type: serverWidget.FieldType.SELECT,
    label: 'Item',
    source: 'item'
  });
 
  // Quantity (editable)
  sublist.addField({
    id: 'custpage_qty',
    type: serverWidget.FieldType.INTEGER,
    label: 'Quantity'
  });
 
  // Rate
  sublist.addField({
    id: 'custpage_rate',
    type: serverWidget.FieldType.CURRENCY,
    label: 'Rate'
  });
 
  return sublist;
};
 
// Reading sublist data on POST
const readSublistData = (request) => {
  const lineCount = request.getLineCount({ group: 'custpage_line_items' });
  const items = [];
 
  for (let i = 0; i < lineCount; i++) {
    items.push({
      item: request.getSublistValue({
        group: 'custpage_line_items',
        name: 'custpage_item',
        line: i
      }),
      quantity: request.getSublistValue({
        group: 'custpage_line_items',
        name: 'custpage_qty',
        line: i
      }),
      rate: request.getSublistValue({
        group: 'custpage_line_items',
        name: 'custpage_rate',
        line: i
      })
    });
  }
 
  return items;
};

Adding Client Script for Interactivity

Attach a Client Script for real-time validation:

// In your Suitelet, reference the client script:
form.clientScriptModulePath = './MyClientScript.js';
 
// Or inline:
form.clientScriptFileId = 12345; // File Cabinet ID

Sample Client Script:

/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 */
define(['N/currentRecord'], (currentRecord) => {
  const pageInit = (context) => {
    console.log('Form loaded');
  };
 
  const fieldChanged = (context) => {
    if (context.fieldId === 'custpage_customer') {
      // Auto-populate email from customer record
      const rec = currentRecord.get();
      // Fetch customer data...
    }
  };
 
  const saveRecord = (context) => {
    const rec = currentRecord.get();
    const description = rec.getValue({ fieldId: 'custpage_description' });
 
    if (description.length < 20) {
      alert('Please provide a more detailed description (min 20 characters)');
      return false;
    }
 
    return true;
  };
 
  return { pageInit, fieldChanged, saveRecord };
});

Deployment and URL Access

After deploying your Suitelet:

  1. Create a Script record pointing to your file
  2. Create a Script Deployment
  3. Set "Available Without Login" if it's a public form
  4. Get the External URL from the deployment record

The URL format is:

https://ACCOUNT_ID.app.netsuite.com/app/site/hosting/scriptlet.nl?script=SCRIPT_ID&deploy=DEPLOY_ID

Best Practices

  1. Validate on both client and server - Never trust client-side validation alone
  2. Use field groups - Organize complex forms logically
  3. Handle errors gracefully - Show user-friendly messages
  4. Log important actions - Audit trail for troubleshooting
  5. Consider governance - Suitelets have 1,000 unit limits

Security Considerations

  • Always validate user permissions
  • Sanitize input to prevent injection attacks
  • Use HTTPS for sensitive data
  • Consider role-based access in deployment settings

Next Steps

Now that you can build Suitelets, explore:


Need custom Suitelet development for your business? Contact us to discuss your requirements.

Need hands-on training?

Our corporate training programs go beyond tutorials with personalized instruction using your actual NetSuite environment.

Get in Touch