🔹 Introduction
Saved Searches let you pull data from NetSuite.
- Admins build them in the UI.
- Developers use them in SuiteScript to automate or process results.
This page shows how to:
- Load existing Saved Searches.
- Create ad-hoc searches directly in code.
- Use them in Map/Reduce scripts.
- Modify filters dynamically.
🔹 Example 1: Load an Existing Saved Search
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define(['N/search'], (search) => {
const execute = () => {
try {
// Load a saved search that was created in the NetSuite UI
// Use its internal ID (you can find this in the search definition)
const customerSearch = search.load({
id: 'customsearch_my_customer_search' // <-- replace with your saved search ID
});
// Run the search and iterate through results
// .each() lets you go row by row
customerSearch.run().each(result => {
// Get the values for each column
const name = result.getValue('entityid'); // Customer name/ID
const email = result.getValue('email'); // Customer email address
// Log output for debugging
log.debug('Customer', `${name} | ${email}`);
return true; // MUST return true to continue iteration
});
} catch (e) {
log.error('Error loading saved search', e.message);
}
};
return { execute };
});
💡 Explanation:
- Use this when admins maintain search logic in the UI.
- Developers simply reference the search by ID.
🔹 Example 2: Create an Ad-Hoc Search
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define(['N/search'], (search) => {
const execute = () => {
try {
// Create a search fully in code (no UI search needed)
const customerSearch = search.create({
type: search.Type.CUSTOMER, // Search on Customer records
filters: [['isinactive', 'is', 'F']], // Only active customers
columns: ['entityid', 'email'] // Return Customer Name + Email
});
// Run the search and fetch first 5 results only
const results = customerSearch.run().getRange({ start: 0, end: 5 });
// Loop through results and log
results.forEach(result => {
const name = result.getValue('entityid');
const email = result.getValue('email');
log.debug('Customer', `${name} | ${email}`);
});
} catch (e) {
log.error('Error in ad-hoc search', e.message);
}
};
return { execute };
});
💡 Explanation:
- This method gives full control in code.
- Use
.getRange()
for small batches (use Map/Reduce for large sets).
🔹 Example 3: Saved Search in Map/Reduce
/**
*@NApiVersion 2.1
*@NScriptType MapReduceScript
*/
define(['N/search'], (search) => {
// getInputData defines where the Map/Reduce pulls input from
const getInputData = () => {
try {
// Load a saved search of open Sales Orders
return search.load({
id: 'customsearch_open_salesorders' // Replace with your saved search ID
});
} catch (e) {
log.error('Error in getInputData', e.message);
}
};
// map runs once per search result
const map = (context) => {
try {
// context.value is a JSON string of the result
const result = JSON.parse(context.value);
log.debug('Map Stage', `SO ID: ${result.id}, Customer: ${result.values.entity}`);
// Write forward key/value pairs to reduce stage
context.write({
key: result.values.entity.value, // Customer ID
value: result.id // Sales Order ID
});
} catch (e) {
log.error('Error in map', e.message);
}
};
// reduce consolidates data for each key
const reduce = (context) => {
try {
log.debug('Reduce Stage', `Customer: ${context.key}, Orders: ${context.values.length}`);
} catch (e) {
log.error('Error in reduce', e.message);
}
};
// summarize runs once after everything
const summarize = (summary) => {
try {
log.debug('Summary', `Usage Consumed: ${summary.usage}`);
} catch (e) {
log.error('Error in summarize', e.message);
}
};
return { getInputData, map, reduce, summarize };
});
💡 Explanation:
- Perfect for large data jobs.
- Admins build the Saved Search, developers process it via Map/Reduce.
🔹 Example 4: Modify Saved Search Filters Dynamically
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define(['N/search'], (search) => {
const execute = () => {
try {
// Load base Sales Order search from the UI
const soSearch = search.load({
id: 'customsearch_open_salesorders'
});
// Add extra filter at runtime: Only Sales Orders for Customer ID = 123
soSearch.filters.push(['entity', 'anyof', '123']);
// Get first 3 results
const results = soSearch.run().getRange({ start: 0, end: 3 });
results.forEach(result => {
const soId = result.id;
const customerName = result.getText('entity');
log.debug('Filtered SO', `SO ID: ${soId} | Customer: ${customerName}`);
});
} catch (e) {
log.error('Error modifying saved search', e.message);
}
};
return { execute };
});
💡 Explanation:
- Useful if you want a base Saved Search but add filters dynamically (e.g., by customer, subsidiary, or date).
🔹 Best Practices
- Use
search.load()
when admins maintain criteria in the UI. - Use
search.create()
for dev-only automation. - Use
.each()
for streaming large result sets (but be mindful of governance). - Use
.getRange()
for smaller chunks of data. - In Map/Reduce, always pass Saved Searches in
getInputData
. - Wrap everything in try–catch and log errors for debugging.
✅ Key Takeaway
Saved Searches are the bridge between admins and developers. Admins build the logic in the UI, and developers use that logic in SuiteScript for automation, reporting, and large-scale data jobs.