Skip to main content

Saved Searches

Saved Searches are pre-configured queries that can be reused in reports, dashboards, scripts, and other NetSuite features.


When to Use Saved Searches

Use CaseExample
ReportsMonthly sales by region
DashboardsReal-time KPI displays
PortletsCustomer-facing data
ScriptsData source for automation
FormsDropdown field sources
WorkflowsCondition evaluation

Saved Search Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│ SAVED SEARCH STRUCTURE │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ SAVED SEARCH DEFINITION │
│ customsearch_pending_approvals │
└──────────────────────────────┬───────────────────────────────────┘

┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ FILTERS │ │ COLUMNS │ │ SETTINGS │
│ (Criteria) │ │ (Results) │ │ (Options) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ • Status = Pend │ │ • Transaction # │ │ • Public │
│ • Date > Today │ │ • Customer │ │ • Audience │
│ • Amount > 1000 │ │ • Amount │ │ • Update freq │
└─────────────────┘ │ • Date │ │ • Alert │
│ • Approver │ └─────────────────┘
└─────────────────┘

Basic Saved Search XML

<?xml version="1.0" encoding="UTF-8"?>
<savedsearch scriptid="customsearch_pending_approvals">
<definition>
<!-- Search Type -->
<searchtype>Transaction</searchtype>

<!-- Search Title and Access -->
<title>Pending Approvals</title>
<description>All transactions pending approval</description>
<ispublic>T</ispublic>
<isdefaultsearch>F</isdefaultsearch>

<!-- Result Columns -->
<searchcolumns>
<column>
<name>tranid</name>
<label>Document Number</label>
</column>
<column>
<name>trandate</name>
<label>Date</label>
<sort>DESC</sort>
</column>
<column>
<name>entity</name>
<label>Customer/Vendor</label>
</column>
<column>
<name>amount</name>
<label>Amount</label>
</column>
<column>
<name>custbody_approval_status</name>
<label>Approval Status</label>
</column>
</searchcolumns>

<!-- Search Filters -->
<searchfilters>
<filter>
<name>mainline</name>
<operator>is</operator>
<values>T</values>
</filter>
<filter>
<name>custbody_approval_status</name>
<operator>anyof</operator>
<values>1</values> <!-- Pending -->
</filter>
<filter>
<name>trandate</name>
<operator>onorafter</operator>
<values>daysago30</values>
</filter>
</searchfilters>

</definition>

<!-- Role Access -->
<audience>
<allroles>F</allroles>
<audslctrole>
<role>Administrator</role>
</audslctrole>
<audslctrole>
<role>A/P Clerk</role>
</audslctrole>
</audience>

</savedsearch>

Filter Operators

┌─────────────────────────────────────────────────────────────────────────────┐
│ FILTER OPERATORS │
└─────────────────────────────────────────────────────────────────────────────┘

┌────────────────────┬─────────────────────────────────────────────────────────┐
│ TEXT OPERATORS │ │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ is │ Exact match │
│ isnot │ Not equal │
│ contains │ Text contains value │
│ doesnotcontain │ Text does not contain │
│ startswith │ Text starts with │
│ isempty │ Field is empty │
│ isnotempty │ Field has value │
└────────────────────┴─────────────────────────────────────────────────────────┘

┌────────────────────┬─────────────────────────────────────────────────────────┐
│ NUMBER OPERATORS │ │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ equalto │ Equal to value │
│ notequalto │ Not equal to value │
│ lessthan │ Less than value │
│ lessthanorequalto │ Less than or equal │
│ greaterthan │ Greater than value │
│ greaterthanorequalto │ Greater than or equal │
│ between │ Within range (two values) │
│ notbetween │ Outside range │
└────────────────────┴─────────────────────────────────────────────────────────┘

┌────────────────────┬─────────────────────────────────────────────────────────┐
│ DATE OPERATORS │ │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ on │ On specific date │
│ noton │ Not on date │
│ before │ Before date │
│ onorbefore │ On or before date │
│ after │ After date │
│ onorafter │ On or after date │
│ within │ Between two dates │
│ notwithin │ Outside date range │
└────────────────────┴─────────────────────────────────────────────────────────┘

┌────────────────────┬─────────────────────────────────────────────────────────┐
│ SELECT OPERATORS │ │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ anyof │ Any of the listed values │
│ noneof │ None of the listed values │
└────────────────────┴─────────────────────────────────────────────────────────┘

Relative Date Values

┌─────────────────────────────────────────────────────────────────────────────┐
│ RELATIVE DATE VALUES │
└─────────────────────────────────────────────────────────────────────────────┘

today → Current date
yesterday → Previous day
tomorrow → Next day

thisweek → Current week
lastweek → Previous week
nextweek → Following week

thismonth → Current month
lastmonth → Previous month
nextmonth → Following month

thisquarter → Current quarter
lastquarter → Previous quarter
nextquarter → Following quarter

thisyear → Current year
lastyear → Previous year
nextyear → Following year

daysago7 → 7 days ago
daysago30 → 30 days ago
daysago90 → 90 days ago

daysfromnow7 → 7 days from now
daysfromnow30 → 30 days from now

Complete Example: Sales Report

<?xml version="1.0" encoding="UTF-8"?>
<savedsearch scriptid="customsearch_sales_by_region">
<definition>
<searchtype>Transaction</searchtype>
<title>Sales by Region - Monthly</title>
<description>Monthly sales summary grouped by region</description>
<ispublic>T</ispublic>

<!-- Grouping and Summary Columns -->
<searchcolumns>
<column>
<name>custbody_region</name>
<label>Region</label>
<summary>GROUP</summary>
</column>
<column>
<name>salesrep</name>
<label>Sales Rep</label>
<summary>GROUP</summary>
</column>
<column>
<name>internalid</name>
<label>Order Count</label>
<summary>COUNT</summary>
</column>
<column>
<name>amount</name>
<label>Total Sales</label>
<summary>SUM</summary>
</column>
<column>
<name>amount</name>
<label>Average Order</label>
<summary>AVG</summary>
</column>
</searchcolumns>

<searchfilters>
<!-- Transaction Type -->
<filter>
<name>type</name>
<operator>anyof</operator>
<values>SalesOrd</values>
</filter>
<!-- Main line only -->
<filter>
<name>mainline</name>
<operator>is</operator>
<values>T</values>
</filter>
<!-- This month -->
<filter>
<name>trandate</name>
<operator>within</operator>
<values>thismonth</values>
</filter>
<!-- Not cancelled -->
<filter>
<name>status</name>
<operator>noneof</operator>
<values>SalesOrd:C</values>
</filter>
</searchfilters>

</definition>

<audience>
<allroles>F</allroles>
<audslctrole>
<role>Sales Manager</role>
</audslctrole>
<audslctrole>
<role>Administrator</role>
</audslctrole>
</audience>

</savedsearch>

Summary Types

SummaryDescription
GROUPGroup results by this column
COUNTCount of records
SUMSum of numeric values
AVGAverage of numeric values
MINMinimum value
MAXMaximum value

Using Saved Searches in Scripts

const search = require('N/search');

const runSavedSearch = () => {
// Load by script ID
const savedSearch = search.load({
id: 'customsearch_pending_approvals'
});

// Or load by internal ID
// const savedSearch = search.load({ id: 1234 });

const results = [];

savedSearch.run().each((result) => {
results.push({
id: result.id,
tranId: result.getValue('tranid'),
date: result.getValue('trandate'),
entity: result.getText('entity'),
amount: result.getValue('amount'),
status: result.getText('custbody_approval_status')
});

return true; // Continue to next result
});

return results;
};

Modify Saved Search Filters

const search = require('N/search');

const runWithDateFilter = (startDate, endDate) => {
const savedSearch = search.load({
id: 'customsearch_pending_approvals'
});

// Add additional filters
savedSearch.filters.push(
search.createFilter({
name: 'trandate',
operator: search.Operator.WITHIN,
values: [startDate, endDate]
})
);

// Run the modified search
const results = [];

savedSearch.run().each((result) => {
results.push({
id: result.id,
tranId: result.getValue('tranid'),
amount: result.getValue('amount')
});

return true;
});

return results;
};

Get Saved Search ID

// Find saved search by script ID
const findSearchId = (scriptId) => {
const searchSearch = search.create({
type: 'savedsearch',
filters: [
['id', 'is', scriptId]
],
columns: ['internalid']
});

let internalId = null;

searchSearch.run().each((result) => {
internalId = result.getValue('internalid');
return false;
});

return internalId;
};

Search Result Pagination

┌─────────────────────────────────────────────────────────────────────────────┐
│ RESULT PAGINATION │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ run().each() │
│ ────────────────────────────────────────────────────────────────│
│ • Processes up to 4,000 results │
│ • Stops when callback returns false │
│ • Most common approach │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ run().getRange({ start, end }) │
│ ────────────────────────────────────────────────────────────────│
│ • Gets specific range of results │
│ • Maximum 1,000 per call │
│ • Use for pagination │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ runPaged({ pageSize }) │
│ ────────────────────────────────────────────────────────────────│
│ • Handles large result sets │
│ • Page size: 5-1000 │
│ • Returns PagedData object │
└──────────────────────────────────────────────────────────────────┘

Paged Search Example

const runPagedSearch = () => {
const savedSearch = search.load({
id: 'customsearch_all_customers'
});

const pagedData = savedSearch.runPaged({
pageSize: 100
});

log.debug('Total Results', pagedData.count);
log.debug('Total Pages', pagedData.pageRanges.length);

const allResults = [];

pagedData.pageRanges.forEach((pageRange) => {
const page = pagedData.fetch({ index: pageRange.index });

page.data.forEach((result) => {
allResults.push({
id: result.id,
name: result.getValue('companyname')
});
});
});

return allResults;
};

Joined Searches

Search across related records:

<?xml version="1.0" encoding="UTF-8"?>
<savedsearch scriptid="customsearch_customer_orders">
<definition>
<searchtype>Transaction</searchtype>
<title>Customer Orders with Details</title>

<searchcolumns>
<!-- Transaction columns -->
<column>
<name>tranid</name>
</column>
<column>
<name>trandate</name>
</column>
<column>
<name>amount</name>
</column>

<!-- Joined customer columns -->
<column>
<name>companyname</name>
<join>customer</join>
</column>
<column>
<name>email</name>
<join>customer</join>
</column>
<column>
<name>salesrep</name>
<join>customer</join>
</column>

<!-- Joined item columns (for line items) -->
<column>
<name>itemid</name>
<join>item</join>
</column>
</searchcolumns>

<searchfilters>
<filter>
<name>type</name>
<operator>anyof</operator>
<values>SalesOrd</values>
</filter>

<!-- Filter by joined field -->
<filter>
<name>custentity_region</name>
<join>customer</join>
<operator>anyof</operator>
<values>1</values>
</filter>
</searchfilters>

</definition>
</savedsearch>

Formula Columns

<searchcolumns>
<!-- Formula: Days since order -->
<column>
<name>formulanumeric</name>
<formula>{today}-{trandate}</formula>
<label>Days Outstanding</label>
</column>

<!-- Formula: Concatenate fields -->
<column>
<name>formulatext</name>
<formula>{tranid} || ' - ' || {entity}</formula>
<label>Order Reference</label>
</column>

<!-- Formula: Conditional -->
<column>
<name>formulatext</name>
<formula>CASE WHEN {amount} > 10000 THEN 'High Value' ELSE 'Standard' END</formula>
<label>Order Type</label>
</column>
</searchcolumns>

Search in Suitelet (Results Display)

const showSearchResults = (context) => {
const form = serverWidget.createForm({
title: 'Pending Approvals'
});

// Load and run search
const savedSearch = search.load({
id: 'customsearch_pending_approvals'
});

// Add sublist
const sublist = form.addSublist({
id: 'results',
type: serverWidget.SublistType.LIST,
label: 'Results'
});

// Add columns matching search
sublist.addField({ id: 'tranid', type: serverWidget.FieldType.TEXT, label: 'Document #' });
sublist.addField({ id: 'date', type: serverWidget.FieldType.DATE, label: 'Date' });
sublist.addField({ id: 'entity', type: serverWidget.FieldType.TEXT, label: 'Entity' });
sublist.addField({ id: 'amount', type: serverWidget.FieldType.CURRENCY, label: 'Amount' });

// Populate from search
let line = 0;
savedSearch.run().each((result) => {
sublist.setSublistValue({ id: 'tranid', line: line, value: result.getValue('tranid') || '' });
sublist.setSublistValue({ id: 'date', line: line, value: result.getValue('trandate') || '' });
sublist.setSublistValue({ id: 'entity', line: line, value: result.getText('entity') || '' });
sublist.setSublistValue({ id: 'amount', line: line, value: result.getValue('amount') || 0 });

line++;
return line < 500; // Limit results
});

context.response.writePage(form);
};

Import Saved Searches

# Import all saved searches
suitecloud object:import --type savedsearch --destinationfolder Objects

# Import specific search
suitecloud object:import --type savedsearch --scriptid customsearch_pending_approvals --destinationfolder Objects

Best Practices

PracticeDescription
Meaningful namesDescriptive script IDs and titles
Limit columnsOnly include needed columns
Optimize filtersUse indexed fields in filters
Set audienceRestrict to appropriate roles
Document purposeInclude clear descriptions
Test performanceCheck execution time

Next Steps