Skip to main content
Oct 25–28SuiteWorld 2026 — Early bird ends Jul 31
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