Zero-code reusable transaction search tool for NetSuite supporting 100+ batch lookups
A single reusable Suitelet that searches multiple transaction records at once using script parameters. No code changes required when adding new transaction types.
Users need to look up details for 100+ transaction document numbers, but NetSuite's saved search interface requires entering values one at a time:
- Manual Process: Enter each document number individually → Click Search → Copy results
- Time Cost: 30-60 seconds per lookup = 50+ minutes for 100 transactions
- Repetitive Development: Building a separate search tool for each transaction type (Item Fulfillments, Transfer Orders, Sales Orders) is wasteful
A single Suitelet script serves every transaction type through deployment parameters:
┌─────────────────────────┐
│ Single Suitelet │
│ (One Script File) │
└───────────┬─────────────┘
│
┌───────┴───────┬───────────┬─────────────┐
│ │ │ │
Deployment 1 Deployment 2 Deployment 3 Deployment N
IF Search TO Search SO Search Any Type
↓ ↓ ↓ ↓
Parameters: Parameters: Parameters: Parameters:
- Search ID - Search ID - Search ID - Search ID
- Type Label - Type Label - Type Label - Type Label
- Field Name - Field Name - Field Name - Field Name
Key Insight: The search logic is identical across transaction types. Only the saved search ID and display labels change.
| Parameter | Purpose | Example |
|---|---|---|
| Saved Search ID | Which search to load and filter | customsearch4376 |
| Transaction Type | Display label for UI | Item Fulfillment |
| Page Title | Browser title | Wholesale IF Lookup |
| Document Field | Which field to filter on | tranid (default) |
- Paste - User pastes 100+ document numbers into textarea (commas, spaces, newlines, OR-separated)
- Submit - Click Submit button
- Results - See table with all matching transactions
/**
* @NApiVersion 2.1
* @NScriptType Suitelet
*/
define(['N/search', 'N/runtime', 'N/ui/serverWidget'],
function(search, runtime, serverWidget) {
function onRequest(context) {
const script = runtime.getCurrentScript();
// Read deployment parameters
const savedSearchId = script.getParameter({ name: 'custscript_search_id' });
const transactionType = script.getParameter({ name: 'custscript_transaction_type' });
const pageTitle = script.getParameter({ name: 'custscript_page_title' });
const documentField = script.getParameter({ name: 'custscript_document_field' }) || 'tranid';
if (context.request.method === 'GET') {
const form = buildSearchForm(pageTitle, transactionType);
context.response.writePage(form);
} else {
const inputText = context.request.parameters.custpage_input;
const documentNumbers = parseInput(inputText);
const results = searchTransactions(savedSearchId, documentField, documentNumbers);
const form = buildResultsForm(pageTitle, transactionType, results);
context.response.writePage(form);
}
}
function parseInput(inputText) {
// Split on commas, spaces, newlines, OR operators
return inputText
.split(/[,\s|]+/)
.map(s => s.trim())
.filter(s => s.length > 0);
}
function searchTransactions(savedSearchId, fieldId, documentNumbers) {
const searchObj = search.load({ id: savedSearchId });
// Use anyof operator for batch filtering
searchObj.filters.push(search.createFilter({
name: fieldId,
operator: search.Operator.ANYOF,
values: documentNumbers
}));
const results = [];
searchObj.run().each(function(result) {
results.push(result);
return true; // Continue iteration
});
return results;
}
return { onRequest };
}
);Instead of running 100 separate searches, use the anyof operator to match multiple values in a single query:
search.createFilter({
name: 'tranid',
operator: search.Operator.ANYOF,
values: ['IF12345', 'IF12346', 'IF12347', ...] // Up to 200 values
});Read column definitions directly from the saved search, so admins can modify columns without touching code:
function getColumnsFromSearch(searchObj) {
const columns = [];
searchObj.columns.forEach(col => {
columns.push({
id: col.name,
label: col.label || col.name,
type: 'TEXT' // Or determine from search metadata
});
});
return columns;
}Accept any delimiter format users naturally use:
function parseInput(inputText) {
// Handles: "IF001, IF002, IF003"
// Handles: "IF001 IF002 IF003"
// Handles: "IF001\nIF002\nIF003"
// Handles: "IF001 OR IF002 OR IF003"
return inputText
.split(/[,\s|]+/)
.map(s => s.trim())
.filter(s => s.length > 0);
}Parameters:
- Saved Search ID:
customsearch_if_lookup - Transaction Type:
Item Fulfillment - Page Title:
Wholesale IF Lookup - Document Field:
tranid
URL: https://1234567.app.netsuite.com/app/site/hosting/scriptlet.nl?script=123&deploy=1
Parameters:
- Saved Search ID:
customsearch_to_lookup - Transaction Type:
Transfer Order - Page Title:
TO Batch Search - Document Field:
tranid
URL: https://1234567.app.netsuite.com/app/site/hosting/scriptlet.nl?script=123&deploy=2
Code Changes Required: ZERO
Before:
- 100 transaction lookups = 50+ minutes of manual work
- Separate tools needed for each transaction type
- 3 hours of development per transaction type
After:
- 100 transaction lookups = 60 seconds (60x faster)
- Single script serves all transaction types
- 5 minutes to deploy new transaction type (create deployment + set parameters)
Reusability Score: 5/5 - Script works for ANY transaction type with zero modifications
- NetSuite limits
anyofoperator to ~1,000 values per filter - Search governance units consumed based on result volume
- Large result sets may require pagination
// Test input parsing
test('parseInput handles comma-separated values', () => {
const input = 'IF001, IF002, IF003';
const result = parseInput(input);
expect(result).toEqual(['IF001', 'IF002', 'IF003']);
});
// Test multiple delimiters
test('parseInput handles mixed delimiters', () => {
const input = 'IF001, IF002 IF003\nIF004|IF005';
const result = parseInput(input);
expect(result).toEqual(['IF001', 'IF002', 'IF003', 'IF004', 'IF005']);
});- Create test saved search with known transactions
- Paste known document numbers
- Verify all expected results appear
- Verify no unexpected results
- Limit result columns - Fewer columns = faster searches and lower governance
- Add pagination - For searches returning 1,000+ results
- Cache saved search objects - Reduce script governance on repeated searches
- Validate input - Reject suspiciously large input (10,000+ values)
- Log failures - Track which document numbers returned no results
MIT License - See LICENSE file for details
- Config-Driven Suitelet - Configuration-driven column rendering
- Multi-Mode Suitelet - Single Suitelet with multiple workflow modes