VERSICH

NetSuite Custom Sublist Buttons for Faster Record Actions

netsuite custom sublist buttons for faster record actions

NetSuite Custom Sublist Buttons for Faster Record Actions

A NetSuite custom sublist with Attach and New buttons gives users a focused workspace for managing related files or records directly from a parent record. Instead of navigating through separate menus, users can review related entries, attach an existing file, or create a new related record from the same NetSuite form.

The reliable implementation combines four parts: a User Event or Suitelet that creates the sublist, a client script that responds to the buttons, a server-side endpoint that performs secure record changes, and a search or record-loading process that repopulates the sublist. The buttons themselves only provide the user interface. They do not automatically attach files or create records, so the underlying actions must be implemented separately.

This distinction is important. A custom sublist is a presentation layer, not a database table. If users need to save rows, maintain relationships, or audit changes, those records must exist as custom records, file attachments, or another persistent NetSuite relationship.

What a NetSuite custom sublist actually does

A NetSuite custom sublist displays additional fields and rows on a record form or Suitelet-created page. The sublist can show information from searches, custom records, transactions, files, or other related data sources.

For example, a parent customer record could display a “Related Documents” sublist containing:

  • File name and file type

  • Document category

  • Upload date

  • Created by

  • Internal ID

  • Link to view or download the file

The rows normally come from a saved search, SuiteScript search, or records loaded by a server-side script. NetSuite then renders those values into the form.

The sublist and its buttons have different responsibilities:

ComponentResponsibility
Custom sublistDisplays related data
Attach buttonStarts a workflow for connecting an existing file or record
New buttonStarts a workflow for creating a new related record
Client scriptHandles browser-side interaction
Suitelet, RESTlet, or User Event logicValidates and performs server-side changes
Search or record queryRetrieves rows for display

This architecture prevents a common mistake: placing business logic directly inside a browser event and assuming the sublist will persist changes. NetSuite record operations should be validated and executed on the server, particularly when the action changes file ownership, record relationships, or transaction data.

For a broader explanation of when a Suitelet is appropriate, see our guide distinguishing [the general Suitelet use cases from this focused sublist customization](/blog/what-is-suitelet-in-netsuite/).

How to add Attach and New buttons to a custom sublist

The implementation begins in the script that builds the form. Depending on where the interface belongs, that script could be a User Event, Suitelet, or another server-side customization that exposes a `serverWidget.Form`.

A User Event is appropriate when the sublist should appear on an existing standard record form. A Suitelet is better when the entire experience requires a custom page, several filters, or a workflow that does not belong directly on the record.

In SuiteScript 2.1, the basic structure looks like this:

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 */
define(['N/ui/serverWidget'], (serverWidget) => {
    const beforeLoad = (context) => {
        if (
            context.type !== context.UserEventType.VIEW &&
            context.type !== context.UserEventType.EDIT
        ) {
            return;
        }

        const form = context.form;

        const sublist = form.addSublist({
            id: 'custpage_related_documents',
            type: serverWidget.SublistType.LIST,
            label: 'Related Documents'
        });

        sublist.addField({
            id: 'custpage_document_name',
            type: serverWidget.FieldType.TEXT,
            label: 'Document'
        });

        sublist.addField({
            id: 'custpage_document_type',
            type: serverWidget.FieldType.TEXT,
            label: 'Type'
        });

        sublist.addField({
            id: 'custpage_document_id',
            type: serverWidget.FieldType.INTEGER,
            label: 'Internal ID'
        });

        sublist.addButton({
            id: 'custpage_attach_document',
            label: 'Attach',
            functionName: 'attachDocument'
        });

        sublist.addButton({
            id: 'custpage_new_document',
            label: 'New',
            functionName: 'createNewDocument'
        });

        form.clientScriptModulePath = './related_documents_client.js';

        loadRelatedDocuments(sublist, context.newRecord.id);
    };

    return { beforeLoad };
});

The exact field definitions depend on the data being displayed. A field that contains an internal ID is useful for client-side selection, but it should not necessarily be visible to users. You can use a hidden field for that value and display a separate text field for the document name.

The `custpage_` prefix is important for fields and sublists created at runtime. Custom page elements should use valid lowercase IDs and should not reuse IDs already present on the standard form.

The script also limits the customization to view and edit contexts. Creating a sublist on create, copy, or delete events may be unnecessary and could expose actions before the parent record has a usable internal ID.

How should the Attach button work?

The Attach button should let a user select an existing file or record and then create the appropriate relationship. The exact workflow depends on what “attach” means in your account.

For a file relationship, the server-side operation may use NetSuite’s `record.attach` method. A file can be attached to a supported record, provided the executing role has permission to access both records and perform the relationship.

The button workflow typically looks like this:

  1. The user clicks Attach.

  2. The client script opens a selection page, modal, or Suitelet.

  3. The user selects an existing file.

  4. The server-side script validates the parent record, selected file, permissions, and duplicate status.

  5. The server-side script creates the attachment.

  6. The page reloads so the custom sublist shows the new relationship.

A client script should not be trusted to decide whether an attachment is valid. The browser can pass a file ID and parent record ID, but the server must confirm that those IDs are valid and that the user is allowed to use them.

A simplified client module could look like this:

/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 */
define(['N/currentRecord', 'N/url'], (currentRecord, url) => {
    const attachDocument = () => {
        const parent = currentRecord.get();

        const attachUrl = url.resolveScript({
            scriptId: 'customscript_document_action_sl',
            deploymentId: 'customdeploy_document_action_sl',
            params: {
                action: 'attach',
                parentId: parent.id,
                parentType: parent.type
            }
        });

        window.open(attachUrl, '_blank', 'width=900,height=700');
    };

    const createNewDocument = () => {
        const parent = currentRecord.get();

        const newUrl = url.resolveScript({
            scriptId: 'customscript_document_action_sl',
            deploymentId: 'customdeploy_document_action_sl',
            params: {
                action: 'new',
                parentId: parent.id,
                parentType: parent.type
            }
        });

        window.open(newUrl, '_blank', 'width=900,height=700');
    };

    return {
        attachDocument,
        createNewDocument
    };
});

The names in this example must match the `functionName` values configured on the sublist buttons. If the function name is wrong, the button can render correctly but produce a browser error when clicked.

The use of `window.open` is only one interface choice. A Suitelet can render a selection form, while a modal-style workflow can provide a more integrated experience. The right choice depends on the number of fields, validation requirements, and whether users need to search through many files.

The New button should open a record creation flow with the parent record already identified. For a standard record, NetSuite’s record URL resolution utilities can help construct a new-record link. For a custom workflow, a Suitelet provides more control.

The parent ID should travel through a controlled parameter, but the server must not assume that a submitted parent ID is correct. The server-side script should verify:

  • The parent record exists.

  • The parent record type is allowed.

  • The current user has access to the parent record.

  • The new record type is permitted for the selected parent.

  • Required values are present.

  • The relationship is not being duplicated.

For example, a New action might create a custom “Document Request” record rather than immediately creating a file. That record could store the parent transaction, document category, due date, owner, and status. A separate process could then upload or attach the final file.

This pattern is stronger than forcing the New button to create a file immediately. NetSuite files have their own metadata and storage behavior, while a custom record can represent approval, review, expiration, or document lifecycle information.

The button label should also reflect the actual result. Use New Document Request when the action creates a request record. Use New File only when the user will create or upload a file as the immediate result.

A custom sublist is populated on the server before NetSuite renders the form. In a User Event, the parent record ID comes from `context.newRecord.id`. The script can then search for related files or custom records and use `sublist.setSublistValue` to write each row.

A simplified population pattern looks like this:

function loadRelatedDocuments(sublist, parentId) {
    const documentSearch = search.create({
        type: 'customrecord_related_document',
        filters: [
            ['custrecord_parent_record', 'anyof', parentId]
        ],
        columns: [
            'name',
            'custrecord_document_type',
            'internalid'
        ]
    });

    let line = 0;

    documentSearch.run().each((result) => {
        sublist.setSublistValue({
            id: 'custpage_document_name',
            line,
            value: result.getValue({ name: 'name' })
        });

        sublist.setSublistValue({
            id: 'custpage_document_type',
            line,
            value: result.getText({ name: 'custrecord_document_type' }) || ''
        });

        sublist.setSublistValue({
            id: 'custpage_document_id',
            line,
            value: String(result.id)
        });

        line += 1;
        return true;
    });
}

In a production script, the `N/search` module must be included in the `define` statement. The search filters and field IDs must match the actual custom record configuration.

Search result limits and governance also matter. `run().each()` processes up to the supported search iteration limit, while larger datasets require pagination or a different display strategy. If the sublist could contain thousands of rows, displaying every related record on the parent form is the wrong design. Use filters, a dedicated Suitelet, or a linked search page instead.

Another practical detail is that sublist values are written as strings. Dates, numbers, and select values need the correct format. A script that passes an object, an unformatted date, or an invalid select value can fail during form rendering.

How do you connect the client script correctly?

The client script must be attached to the form that contains the sublist. Setting `form.clientScriptModulePath` in the server-side script is a direct way to associate a module with a dynamically created form. You can also use a client script deployment when the customization applies more broadly.

The module must export functions with the same names used by the button configuration:

return {
    attachDocument,
    createNewDocument
};

The client module should handle browser-side tasks such as reading the current record, opening a Suitelet, checking whether a record is saved, and refreshing the page after completion. It should not contain the authority for attaching files or bypassing permissions.

A frequent issue occurs when users click New on a record that has not yet been saved. A new record has no internal ID, so a related record cannot safely reference it. The client script should detect that state and display a clear message directing the user to save the parent record first.

Another issue involves popup blockers. Opening a new window only after a direct user click is more reliable than opening one after an asynchronous callback. If the workflow requires a delayed response, consider rendering the action in the current window or using a controlled modal pattern.

What security checks belong on the server?

Security validation belongs on the server-side action script, even when the button is visible only to selected roles. NetSuite client scripts are delivered to the browser, so users can inspect or imitate their requests.

For an Attach action, validate the submitted values and the relationship before calling the attachment operation. A strong implementation checks the record type against an allowlist, confirms the parent record exists, verifies the file is available, blocks duplicate relationships, and records the action where an audit trail is required.

Use role permissions and custom permissions to control access at the NetSuite level. A button can be hidden for a role, but server-side authorization remains essential because a user could attempt to call the Suitelet directly.

Avoid accepting a search filter or record type as unrestricted script parameters. Construct searches from approved field IDs and predefined record types. This prevents both unexpected behavior and accidental exposure of records unrelated to the parent form.

Governance should also be considered. Attaching several files, creating multiple related records, or loading large searches consumes script units. A single Attach action should perform one clear operation, while bulk operations belong in a scheduled script, Map/Reduce script, or controlled batch process.

Custom sublist buttons versus a Suitelet page

A custom sublist is best when users need a compact related-record view inside an existing record. A Suitelet is better when the workflow needs substantial filtering, multi-step validation, file upload, or a large result set.

RequirementCustom sublist on recordDedicated Suitelet
Show a short related-record listStrong fitMore than necessary
Add Attach and New actionsStrong fitStrong fit
Search thousands of recordsLimitedStrong fit
Multi-step file selectionPossible through a linked actionStrong fit
Persist editable rows directlyRequires separate recordsRequires separate records
Reuse across multiple record typesRequires careful deployment logicEasier to centralize
Keep users on the current recordStrong fitDepends on navigation design

The two approaches can work together. The record form can show the most relevant related rows, while the Attach button opens a Suitelet that provides searching, filtering, and validation.

Testing checklist for Attach and New buttons

Test the complete workflow in a sandbox before deploying it to production. The most important test is not whether the buttons appear, but whether the resulting relationship, permissions, and refresh behavior are correct.

Check the following scenarios:

  • View mode and edit mode

  • Unsaved parent records

  • Users without attachment permissions

  • Invalid or missing file IDs

  • Duplicate attachments

  • Inactive or inaccessible related records

  • Parent records from different record types

  • Popup blockers and browser behavior

  • Navigation back to the original record

  • Search results with no rows

  • Large result sets

  • Role-specific visibility

  • Failed server-side actions and readable error messages

Also test deployment context. A User Event may run in contexts where a UI form is not available or where rendering is irrelevant, such as CSV import, web services, or scheduled processing. The `beforeLoad` logic should limit UI-only behavior to appropriate contexts without interfering with non-UI operations.

Common implementation mistakes

The most common mistake is treating the custom sublist as persistent storage. Rows displayed with `setSublistValue` are generated for that page view. They do not become records simply because the user can see them.

Another mistake is adding a button without deploying the matching client script. The button renders, but its function is unavailable. Function names are case-sensitive, and the client module must be associated with the correct form.

Developers also create fragile links by hardcoding internal NetSuite paths. Use NetSuite URL utilities where possible, and pass only the parameters the target script needs. This reduces breakage when account settings, domains, or deployment identifiers change.

Finally, avoid loading every related record during `beforeLoad`. A slow User Event makes the entire record form feel broken. Return only useful columns, limit the initial result set, and move complex selection work to a Suitelet.

When should we help with this customization?

A custom sublist with Attach and New buttons crosses several NetSuite areas: SuiteScript, form rendering, client events, saved searches, permissions, file relationships, and deployment governance. A small error in any one layer can produce a button that looks correct but fails during the actual business process.

We can help design the data relationship, choose between a User Event and Suitelet, implement SuiteScript 2.1 modules, secure server-side actions, and test the result across roles and record contexts. Contact Versich to discuss your NetSuite customization requirements.

Conclusion

A NetSuite custom sublist with Attach and New buttons creates a more efficient related-record workflow, but the visible buttons are only one part of the solution. The sublist displays data, the client script manages interaction, and secure server-side logic performs the actual attachment or record creation.

For dependable results, build the customization around clear record relationships, saved parent records, permission checks, controlled parameters, and manageable search results. Use a custom sublist for quick access inside a record, and use a Suitelet when the workflow requires deeper search, validation, or multi-step processing. With that structure, Attach and New actions become maintainable NetSuite features rather than fragile interface shortcuts.

Frequently Asked Questions

How do I add buttons to a custom sublist in NetSuite?

Create the sublist with `form.addSublist`, then add buttons with `sublist.addButton`. Each button needs a unique ID, a label, and a `functionName` that matches an exported function in the attached client script.

Can a NetSuite custom sublist attach files by itself?

No. The sublist button only starts the process. A client script can open a selection interface, but a server-side Suitelet, RESTlet, or other approved script must validate the request and create the file relationship.

Is a client script required for Attach and New buttons?

Yes, when the buttons need to perform browser-side actions such as opening a Suitelet, reading the current record, or checking whether the parent record is saved. The client script should coordinate the interface, while server-side code performs authorization and record changes.

What is the difference between a custom sublist and a Suitelet in NetSuite?

A custom sublist adds a compact related-data view to an existing record form. A Suitelet creates a separate custom page and is better for advanced filtering, multi-step workflows, file selection, and large datasets.

How much does it cost to add custom sublist buttons in NetSuite?

The cost depends on whether the requirement involves only form rendering or also includes custom records, file attachment logic, permissions, error handling, testing, and deployment. A simple display-only sublist is smaller than a secure Attach and New workflow with multiple record types.

Can users click New before the parent NetSuite record is saved?

They should not. Before the parent record is saved, it has no reliable internal ID, so the new related record cannot be linked correctly. The client script should detect the missing ID and instruct the user to save the parent record first.