If you’ve ever needed to pull complex data from NetSuite using SuiteScript, you’ve likely wrestled with saved searches. They work, but they have limits. Enter the N/query module β NetSuite’s modern, powerful way to run SQL-like queries directly from your scripts using SuiteQL.
In this guide, you’ll learn everything you need to know about the N/query module: what it is, how it works, when to use it, and how to write your first real-world query with a detailed, step-by-step example.
What Is the N/query Module?
The N/query module is a SuiteScript 2.x API that lets you run SuiteQL queries against NetSuite’s database. SuiteQL is based on standard SQL, which means if you know SQL, you already know most of what you need.
Unlike saved searches (which use NetSuite’s own search DSL), the N/query module gives you:
- Full SQL-style querying β JOIN tables, use subqueries, filter with WHERE, sort with ORDER BY, and group with GROUP BY.
- Access to system tables β Query internal NetSuite tables that saved searches can’t easily reach.
- Better performance on large datasets β SuiteQL can be faster and more efficient than chaining multiple saved searches.
- Readable, maintainable code β SQL is a well-known language, making your scripts easier to understand and maintain.
When Should You Use N/query Instead of Saved Searches?
Use the N/query module when:
- You need to JOIN multiple record types in a single query (e.g., Sales Orders + Customers + Items).
- You need to aggregate data (SUM, COUNT, AVG) across large result sets.
- You want to query transaction line-level data efficiently.
- You need access to fields or tables not exposed in saved searches.
- Your saved search is hitting the 1,000-result limit and paging is getting complicated.
How to Load the N/query Module
Like all SuiteScript modules, you need to require it at the top of your script:
/**
* @NApiVersion 2.1
* @NScriptType Suitelet
*/
define(['N/query', 'N/log'], function(query, log) {
// Your script logic here
});
Core Methods of the N/query Module
The N/query module has two main ways to run a query:
| Method | Description | Best For |
|---|---|---|
query.runSuiteQL() | Run a SuiteQL query synchronously | Server-side scripts (Scheduled, Suitelet, Map/Reduce) |
query.runSuiteQLPaged() | Run a paged SuiteQL query | Large result sets that need pagination |
Real-World Example: Get All Open Sales Orders with Customer and Total Amount
Let’s walk through a practical, real-world example. The goal is to retrieve all open Sales Orders, along with the customer name, order date, order number, and total amount β all in a single query.
Step 1: Understand the Tables We Need
In SuiteQL, NetSuite record types map to internal table names. Here are the key tables for our example:
- transaction β Stores all transaction records (Sales Orders, Invoices, Purchase Orders, etc.)
- customer β Stores customer records
We’ll JOIN these two tables using the customer’s internal ID.
Step 2: Write the SuiteQL Query
Here is the complete SuiteScript 2.1 code. Each line is explained with comments so it’s easy to follow:
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
* @NModuleScope SameAccount
*
* Script: Get Open Sales Orders Using N/query (SuiteQL)
* Purpose: Retrieve all open Sales Orders with customer name and total amount
*/
define(['N/query', 'N/log'], function(query, log) {
function execute(context) {
// Step 1: Write the SuiteQL query
// We're selecting key fields from the 'transaction' table
// and joining it with the 'customer' table to get the customer's name.
var suiteQL = `
SELECT
t.id AS transaction_id,
t.tranid AS order_number,
t.trandate AS order_date,
t.amount AS total_amount,
c.entityid AS customer_id,
c.companyname AS customer_name
FROM
transaction t
INNER JOIN
customer c ON c.id = t.entity
WHERE
t.type = 'SalesOrd' -- Filter only Sales Orders
AND t.status = 'A' -- 'A' = Open/Pending Fulfillment
ORDER BY
t.trandate DESC -- Most recent orders first
`;
// Step 2: Run the SuiteQL query using query.runSuiteQL()
// This returns a ResultSet object that we can iterate through.
var resultSet = query.runSuiteQL({ query: suiteQL });
// Step 3: Use .asMappedResults() to convert each row into
// a plain JavaScript object, making it easy to access fields by name.
var results = resultSet.asMappedResults();
// Step 4: Log the number of results found
log.audit({
title: 'Open Sales Orders Found',
details: 'Total records: ' + results.length
});
// Step 5: Loop through each result and log the details
results.forEach(function(row) {
log.audit({
title: 'Sales Order: ' + row.order_number,
details: JSON.stringify({
Customer: row.customer_name,
OrderDate: row.order_date,
TotalAmount: '$' + parseFloat(row.total_amount).toFixed(2)
})
});
});
}
return { execute: execute };
});
Step 3: Understanding the Output
When you run this script, you’ll see entries in the NetSuite execution log like this:
Title: Open Sales Orders Found
Details: Total records: 47
Title: Sales Order: SO-1042
Details: {
"Customer": "Acme Corporation",
"OrderDate": "2026-06-15",
"TotalAmount": "$4,250.00"
}
Title: Sales Order: SO-1038
Details: {
"Customer": "TechBridge Solutions",
"OrderDate": "2026-06-10",
"TotalAmount": "$1,800.50"
}
Working with Paged Results for Large Datasets
If your query might return thousands of rows, use query.runSuiteQLPaged() to process results in pages (up to 1,000 rows per page) without hitting governance limits.
// Use runSuiteQLPaged() for large result sets
var pagedData = query.runSuiteQLPaged({
query: suiteQL,
pageSize: 500 // Process 500 rows at a time
});
// Iterate through each page
pagedData.pageRanges.forEach(function(pageRange) {
var page = pagedData.fetch({ index: pageRange.index });
var rows = page.data.asMappedResults();
rows.forEach(function(row) {
// Process each row here
log.debug('Processing Order', row.order_number);
});
});
Common SuiteQL Tips and Gotchas
Here are some practical tips that will save you time when working with the N/query module:
1. Use BUILTIN.DF() to Get Display Values
By default, SuiteQL returns internal IDs for list fields (like status, type, etc.). Use the BUILTIN.DF() function to get the human-readable display value:
SELECT
t.id,
BUILTIN.DF(t.status) AS status_display, -- e.g., "Pending Fulfillment" instead of "A"
BUILTIN.DF(t.entity) AS customer_name -- Display name instead of internal ID
FROM
transaction t
WHERE
t.type = 'SalesOrd'
2. SuiteQL is Case-Insensitive for Keywords
You can write SELECT, select, or Select β SuiteQL doesn’t care. However, table names and field names are case-sensitive in some contexts, so it’s best to match them exactly as documented in NetSuite’s SuiteQL tables reference.
3. Find Table and Field Names
You can find all available SuiteQL table names and field names by navigating to Setup > Query > SuiteQL Query Tool in your NetSuite account. This built-in tool lets you write and test queries interactively before putting them in your script.
4. Watch Your Governance
Each call to query.runSuiteQL() uses 10 governance units. This is generally very efficient, but if you’re running queries in a loop, make sure you’re not exhausting your script’s governance budget. Use runtime.getCurrentScript().getRemainingUsage() to monitor this.
Summary
The N/query module is one of the most useful tools in your SuiteScript toolkit. It gives you the power of SQL directly inside NetSuite, making complex data retrieval cleaner, faster, and more maintainable than ever before.
Here’s a quick recap of what we covered:
- The N/query module lets you run SuiteQL (SQL-based) queries from SuiteScript.
- Use
query.runSuiteQL()for straightforward queries andquery.runSuiteQLPaged()for large datasets. - Use
.asMappedResults()to easily access result fields by name. - Use
BUILTIN.DF()to get human-readable display values for list/select fields. - Test your SuiteQL queries in the SuiteQL Query Tool before embedding them in scripts.
Ready to take your NetSuite scripting to the next level? Start replacing complex saved search chains with clean, readable SuiteQL queries today!
Discover more from The NetSuite Pro
Subscribe to get the latest posts sent to your email.
Leave a Reply