NewNetSuite 2025.2 — What's new

SuiteQL: Complete Query Language Guide for NetSuite (2026)

Learn SuiteQL, NetSuite query language based on SQL. Syntax, query examples, SuiteQL Analytics Workbook, vs saved searches, and practical use cases for developers.

7 min read
SuiteQL: Complete Query Language Guide for NetSuite (2026)

SuiteQL for NetSuite: what it is and why it matters

SuiteQL is NetSuite's SQL-like query language that lets you query the NetSuite database using familiar SQL syntax. If you've ever been frustrated by saved search limitations — the inability to do proper JOINs, complex aggregations, or subqueries — SuiteQL is the answer.

TL;DR: SuiteQL is NetSuite's SQL-based query language that supports JOINs, subqueries, and aggregations that saved searches cannot handle. You run it via SuiteScript 2.x's N/query module, SuiteAnalytics Workbook, or the REST API, and it's the best option for developers building integrations, custom reports, and complex data extraction from NetSuite.

Introduced as part of NetSuite's Analytics Workbook and SuiteScript 2.x, SuiteQL provides a SQL interface to NetSuite's data model. You write SELECT statements with JOINs, WHERE clauses, GROUP BY, HAVING, ORDER BY — the standard SQL constructs that every developer knows. The difference from traditional SQL is that you're querying NetSuite's record types and fields rather than raw database tables.

SuiteQL doesn't replace saved searches entirely. Saved searches are still better for end-user reporting, dashboard portlets, and cases where non-technical users need to modify the query. But for developers building integrations, custom reports, and data extraction workflows, SuiteQL is more powerful and more readable than the equivalent saved search API.


SuiteQL syntax fundamentals

Basic SELECT

SELECT id, entityid, companyname, email
FROM customer
WHERE isinactive = 'F'
ORDER BY companyname

This returns active customers with their ID, entity ID, company name, and email. The record type (customer) maps to NetSuite's Customer record. Field names match the internal IDs you'd find in NetSuite's Records Browser.

JOINs

SELECT
  t.tranid,
  t.trandate,
  c.companyname,
  t.foreigntotal
FROM transaction t
INNER JOIN customer c ON t.entity = c.id
WHERE t.type = 'SalesOrd'
  AND t.trandate >= TO_DATE('2026-01-01', 'YYYY-MM-DD')
ORDER BY t.trandate DESC

JOINs work like standard SQL. This query joins transactions to customers, filtering for sales orders in 2026. This kind of cross-record query is cumbersome in saved searches but natural in SuiteQL.

Aggregations

SELECT
  c.companyname,
  COUNT(t.id) AS order_count,
  SUM(t.foreigntotal) AS total_revenue
FROM transaction t
INNER JOIN customer c ON t.entity = c.id
WHERE t.type = 'SalesOrd'
  AND t.trandate >= TO_DATE('2025-01-01', 'YYYY-MM-DD')
GROUP BY c.companyname
HAVING SUM(t.foreigntotal) > 10000
ORDER BY total_revenue DESC

GROUP BY and HAVING work as expected. This gives you customer order counts and revenue totals where total revenue exceeds $10,000.

Subqueries

SELECT id, entityid, companyname
FROM customer
WHERE id IN (
  SELECT entity
  FROM transaction
  WHERE type = 'SalesOrd'
    AND trandate >= TO_DATE('2026-01-01', 'YYYY-MM-DD')
)

Subqueries are one of SuiteQL's biggest advantages over saved searches. Finding customers who placed orders this year requires a subquery — something saved searches can't do natively without workarounds.


Running SuiteQL queries

In SuiteScript 2.x

The primary programmatic interface for SuiteQL is through the N/query module in SuiteScript 2.x:

define(['N/query'], function(query) {
  var results = query.runSuiteQL({
    query: `SELECT id, companyname, email
            FROM customer
            WHERE isinactive = 'F'
            LIMIT 100`
  });
 
  var mappedResults = results.asMappedResults();
  // Returns array of objects: [{id: 1, companyname: 'Acme', email: '...'}]
});

The asMappedResults() method returns an array of objects with field names as keys — much cleaner than working with saved search Result objects.

In SuiteAnalytics Workbook

SuiteAnalytics Workbook provides a visual query builder that generates SuiteQL behind the scenes. You can also write raw SuiteQL directly in the workbook. This is useful for ad-hoc data exploration without writing SuiteScript.

Via REST API

SuiteQL queries can be executed through NetSuite's REST API (SuiteTalk REST Web Services), making it useful for external integrations that need to pull data from NetSuite:

POST /services/rest/query/v1/suiteql
Content-Type: application/json

{
  "q": "SELECT id, companyname FROM customer WHERE isinactive = 'F' LIMIT 10"
}

This enables external applications to query NetSuite data using SQL syntax rather than the more verbose SOAP or REST record APIs.


SuiteQL vs saved searches

CapabilitySaved SearchesSuiteQL
JOINsLimited (formula-based)Full SQL JOINs
SubqueriesNot supportedSupported
Complex aggregationsLimitedFull GROUP BY, HAVING
End-user friendlyYes (UI-based)No (requires SQL knowledge)
Dashboard portletsYesVia Workbook
Scheduled executionYesVia SuiteScript
Permission-based accessYesYes
UNION queriesNoYes

Use saved searches when:

  • End users need to create or modify the query
  • You need dashboard portlets or KPI scoreboards
  • The query is straightforward (single record type, simple filters)

Use SuiteQL when:

  • You need JOINs across multiple record types
  • You need subqueries or UNION queries
  • You're building integrations or custom reports in SuiteScript
  • You need complex aggregations with HAVING clauses
  • You want readable, maintainable query code

Practical examples

Aging AR by customer

SELECT
  c.companyname,
  SUM(CASE WHEN CURRENT_DATE - t.duedate BETWEEN 0 AND 30 THEN t.foreignamountremaining ELSE 0 END) AS current_30,
  SUM(CASE WHEN CURRENT_DATE - t.duedate BETWEEN 31 AND 60 THEN t.foreignamountremaining ELSE 0 END) AS days_31_60,
  SUM(CASE WHEN CURRENT_DATE - t.duedate BETWEEN 61 AND 90 THEN t.foreignamountremaining ELSE 0 END) AS days_61_90,
  SUM(CASE WHEN CURRENT_DATE - t.duedate > 90 THEN t.foreignamountremaining ELSE 0 END) AS over_90
FROM transaction t
INNER JOIN customer c ON t.entity = c.id
WHERE t.type = 'CustInvc'
  AND t.foreignamountremaining > 0
GROUP BY c.companyname
ORDER BY c.companyname

Top items by revenue

SELECT
  i.itemid,
  i.displayname,
  SUM(tl.foreignamount) AS total_revenue,
  SUM(tl.quantity) AS total_quantity
FROM transactionline tl
INNER JOIN transaction t ON tl.transaction = t.id
INNER JOIN item i ON tl.item = i.id
WHERE t.type = 'SalesOrd'
  AND t.trandate >= TO_DATE('2025-01-01', 'YYYY-MM-DD')
  AND tl.mainline = 'F'
GROUP BY i.itemid, i.displayname
ORDER BY total_revenue DESC
FETCH FIRST 20 ROWS ONLY

Customers without orders in 6 months

SELECT c.id, c.companyname, c.email
FROM customer c
WHERE c.isinactive = 'F'
  AND c.id NOT IN (
    SELECT DISTINCT t.entity
    FROM transaction t
    WHERE t.type = 'SalesOrd'
      AND t.trandate >= ADD_MONTHS(CURRENT_DATE, -6)
  )
ORDER BY c.companyname

Tips and gotchas

Record types use internal names. The NetSuite Records Browser (available in your account under Customization > Records Browser) documents available record types and field names. Bookmark it — you'll reference it constantly.

LIMIT your queries during development. SuiteQL can return large result sets. Always add LIMIT 100 during development to avoid timeouts and excessive processing.

Date handling uses Oracle syntax. NetSuite's underlying database is Oracle, so date functions follow Oracle conventions: TO_DATE(), ADD_MONTHS(), CURRENT_DATE, TRUNC().

Transaction lines require explicit mainline filtering. When querying transaction lines, remember that each transaction has a mainline row (summary) and detail rows (line items). Filter with mainline = 'F' for line items or mainline = 'T' for the summary row.

Case sensitivity varies. Record type names are case-insensitive, but string comparisons in WHERE clauses may be case-sensitive depending on the field. Use UPPER() or LOWER() for safe string matching.


Getting started

Start with the NetSuite Records Browser to understand the data model. Pick a saved search you know well and rewrite it in SuiteQL — this gives you a reference point where you know what the correct output should look like.

Then move to queries that saved searches can't handle: cross-record JOINs, subqueries, and complex aggregations. That's where SuiteQL's value becomes obvious.

Frequently Asked Questions

Share:
Joaquin Vigna

Joaquin Vigna

Co-Founder & CTO

Co-founder and Chief Technology Officer at BrokenRubik with 12+ years of experience in software architecture and NetSuite development. Leads technical strategy, innovation initiatives, and ensures delivery excellence across all projects.

12+ years experienceOracle NetSuite Certified +1
Technical ArchitectureSuiteScript DevelopmentNetSuite CustomizationSystem Integration+2 more

Get More Insights Like This

Join our newsletter for weekly tips, tutorials, and exclusive content delivered to your inbox.

Need help with your NetSuite project?

Whether it's integrations, customization, or support — let's talk about how we can help.

What happens next:

  1. 1Tell us about your project or challenge
  2. 2We'll review and get back to you within 24 hours
  3. 3We'll schedule a free consultation to discuss your needs

Tell us about your project

We respond within 24 hours.

Get in Touch