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 Case | Example |
|---|---|
| Reports | Monthly sales by region |
| Dashboards | Real-time KPI displays |
| Portlets | Customer-facing data |
| Scripts | Data source for automation |
| Forms | Dropdown field sources |
| Workflows | Condition 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
| Summary | Description |
|---|---|
| GROUP | Group results by this column |
| COUNT | Count of records |
| SUM | Sum of numeric values |
| AVG | Average of numeric values |
| MIN | Minimum value |
| MAX | Maximum value |
Using Saved Searches in Scripts
Load and Run Saved Search
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
| Practice | Description |
|---|---|
| Meaningful names | Descriptive script IDs and titles |
| Limit columns | Only include needed columns |
| Optimize filters | Use indexed fields in filters |
| Set audience | Restrict to appropriate roles |
| Document purpose | Include clear descriptions |
| Test performance | Check execution time |
Next Steps
- Workflows - Use searches in workflow conditions
- Suitelet Development - Display search results
- Scheduled Script - Process search results