NetSuite SuiteQL Tables Reference: Keys, Columns and Joins
A lookup of the SuiteQL tables in the NetSuite2.com data source that come up in nearly every report, integration and data extract, with primary keys and the join condition that actually works. Use it next to the Records Catalog when you write SuiteQL, N/query or NetSuite2.com ODBC queries.
| Table (SuiteQL) | What it holds | Primary key | Key columns | Common join (ON clause) | Notes |
|---|---|---|---|---|---|
transaction | Header of every transaction: sales orders, invoices, POs, bills, fulfillments, journals, work orders | id | tranid, type, trandate, entity, status, postingperiod | transactionline.transaction = transaction.id | Filter on type (for example 'SalesOrd', 'CustInvc') before anything else. status is a bare letter; use BUILTIN.DF(status) for the label. |
transactionline | Every line of every transaction, including the header (mainline) row and tax rows | transaction + id (uniquekey is unique across all lines) | transaction, id, mainline, taxline, item, quantity, netamount | transactionline.transaction = transaction.id | Add mainline = 'F' for item lines only and taxline = 'F' to drop tax rows. The mainline = 'T' row carries header-level values. |
transactionaccountingline | GL impact of each transaction line per accounting book: account, debit, credit | transaction + transactionline + accountingbook | transaction, transactionline, account, accountingbook, debit, credit, amount, posting | transactionaccountingline.transaction = transactionline.transaction AND transactionaccountingline.transactionline = transactionline.id | Use this, not transactionline, for trial balance and GL reports. Filter posting = 'T' and one accountingbook when Multi-Book Accounting is on. |
item | All item records: inventory, assembly, non-inventory, service, kit, other charge | id | id, itemid, displayname, itemtype, isinactive, parent | transactionline.item = item.id | itemid is the name/number users see. itemtype values include 'InvtPart', 'Assembly', 'NonInvtPart', 'Service', 'Kit'. |
customer | Customer and customer-job records | id | id, entityid, companyname, email, isinactive, terms | transaction.entity = customer.id | transaction.entity points at whichever entity type the transaction uses; join customer only for sales-side types. |
vendor | Vendor (supplier) records | id | id, entityid, companyname, email, isinactive, terms | transaction.entity = vendor.id | Use for purchase orders, vendor bills and vendor payments. |
employee | Employee records, including supervisors and sales reps | id | id, entityid, firstname, lastname, email, supervisor | transaction.employee = employee.id | Employee data is permission sensitive; many integration roles cannot see it. |
entity | Shared parent of customers, vendors, employees, partners and contacts | id | id, entityid, altname, email, isinactive | transaction.entity = entity.id | Use when one query spans sales and purchase transactions and you only need the name. |
contact | Contact people attached to customers, vendors and partners | id | id, entityid, firstname, lastname, email, company | contact.company = customer.id | company holds the parent entity id. |
account | Chart of accounts | id | id, acctnumber, fullname, accttype, isinactive, parent | transactionaccountingline.account = account.id | accttype values include 'Bank', 'AcctRec', 'AcctPay', 'Income', 'COGS', 'Expense'. |
accountingperiod | Fiscal periods, quarters and years | id | id, periodname, startdate, enddate, isquarter, isyear, closed | transaction.postingperiod = accountingperiod.id | Filter isquarter = 'F' AND isyear = 'F' to get month periods only. isadjust flags adjustment periods. |
subsidiary | Legal entities in a OneWorld account | id | id, name, fullname, parent, currency, iselimination | transactionline.subsidiary = subsidiary.id | Subsidiary sits on the line in SuiteQL; the header value is on the mainline row. Exclude iselimination = 'T' for operational reports. |
department | Department segment values | id | id, name, fullname, parent, isinactive | transactionline.department = department.id | fullname includes the parent path, for example 'Operations : Machining'. |
classification | Class segment values | id | id, name, fullname, parent, isinactive | transactionline.class = classification.id | The table is classification but the line column is class. |
location | Warehouses, plants and stores | id | id, name, fullname, parent, subsidiary, isinactive | transactionline.location = location.id | Location on the line is the inventory location for item lines. |
currency | Currencies enabled in the account | id | id, name, symbol, isbasecurrency, isinactive | transaction.currency = currency.id | symbol holds the ISO code such as 'USD' or 'EUR'. Without Multiple Currencies the table holds only the base currency. |
currencyrate | Daily exchange rates between currencies | id | basecurrency, transactioncurrency, exchangerate, effectivedate | currencyrate.transactioncurrency = transaction.currency | Pick the latest effectivedate on or before the transaction date. BUILTIN.CURRENCY_CONVERT usually saves you this join. |
consolidatedexchangerate | Period consolidation rates (current, average, historical) between subsidiaries | id | postingperiod, fromsubsidiary, tosubsidiary, currentrate, averagerate, historicalrate | consolidatedexchangerate.postingperiod = accountingperiod.id | OneWorld only. BUILTIN.CONSOLIDATE applies these rates for you. |
term | Payment terms such as Net 30 | id | id, name, daysuntilnetdue, discountpercent, isinactive | transaction.terms = term.id | Date-driven terms use other columns; check the record in your account. |
systemnote | Audit trail of field changes on records and transactions | id | recordid, recordtypeid, field, oldvalue, newvalue, date, name | systemnote.recordid = transaction.id | name is the user who made the change. Large table: always filter by recordid or date. |
file | File Cabinet files | id | id, name, folder, filetype, filesize, createddate | file.folder = mediaitemfolder.id | Useful for File Cabinet audits and storage clean-up. |
nexttransactionlinelink | Line-level links from a source line to the lines created from it | previousdoc + previousline + nextdoc + nextline | previousdoc, previousline, nextdoc, nextline, linktype | nexttransactionlinelink.previousdoc = transactionline.transaction AND nexttransactionlinelink.previousline = transactionline.id | Trace a sales order line to its fulfillment and invoice lines. linktype tells you the kind of link. |
previoustransactionlinelink | The same line links read from the downstream side | previousdoc + previousline + nextdoc + nextline | previousdoc, previousline, nextdoc, nextline, linktype | previoustransactionlinelink.nextdoc = transactionline.transaction AND previoustransactionlinelink.nextline = transactionline.id | Start from an invoice line and walk back to the order line. |
nexttransactionlink | Header-level links from a transaction to the transactions created from it | previousdoc + nextdoc | previousdoc, nextdoc, linktype | nexttransactionlink.previousdoc = transaction.id | Faster than line links when you only need document to document. |
previoustransactionlink | Header-level links read from the downstream transaction | previousdoc + nextdoc | previousdoc, nextdoc, linktype | previoustransactionlink.nextdoc = transaction.id | Payment to invoice links appear here as well as fulfillment to order links. |
inventoryitemlocations | Quantities per item per location | item + location | item, location, quantityonhand, quantityavailable, quantityonorder, quantitycommitted | inventoryitemlocations.item = item.id | Requires inventory permissions. Values are current, not as of a past date. |
pricelevel | Price levels such as Base Price and Online Price | id | id, name, isinactive | transactionline.price = pricelevel.id | Item prices per level live in a separate pricing table; confirm its name in your Records Catalog. |
unitstype | Units of measure types (for example Each/Box/Case) | id | id, name, isinactive | item.unitstype = unitstype.id | Needs Multiple Units of Measure. transactionline.units holds the unit used on the line. |
bom | Bill of materials headers (Advanced BOM) | id | id, name, isinactive | bomrevision.billofmaterials = bom.id | Only present when Advanced Bill of Materials is enabled. |
bomrevision | Revisions of each BOM with effective dates | id | id, name, billofmaterials, effectivestartdate, effectiveenddate | bomrevision.billofmaterials = bom.id | Pick the revision whose effective range covers your build date. Component lines sit in a child table; check its name in Records Catalog. |
inventorynumber | Lot and serial numbers | id | id, inventorynumber, item, expirationdate | inventorynumber.item = item.id | Needs Lot Tracking or Serialized Inventory. |
customrecord_<scriptid> | Pattern row: each custom record type is its own table named by its script ID | id | id, name, isinactive, owner, created, lastmodified | customrecord_xyz.custrecord_xyz_item = item.id | Custom fields appear as custrecord_* columns. The table name is lowercase script ID, for example customrecord_qc_inspection. |
customlist_<scriptid> | Pattern row: each custom list is a small table of values | id | id, name, isinactive | transaction.custbody_xyz = customlist_xyz.id | Or skip the join and use BUILTIN.DF(custbody_xyz) to get the list value text. |
These are NetSuite2.com (Analytics) data source names used by SuiteQL, N/query, the REST suiteql endpoint and the NetSuite2.com ODBC/JDBC schema. The older NetSuite.com ODBC schema uses different names (for example transactions and transaction_lines), so queries do not port between the two. Which tables and columns you see depends on enabled features and the role's permissions. Confirm any table in Setup > Records Catalog of your own account.
Get your SuiteQL joins checked
Send us a query that returns the wrong totals and we will check the joins and line filters against your Records Catalog.
Optional. The tool and every export work without this. No spam.
How to use it
Search the table by name or by the business object you need, then copy the ON clause. The pattern behind most queries is transaction joined to transactionline, then out to item, entity, account, period and segment tables. For GL numbers go through transactionaccountingline instead of transactionline, because only the accounting line knows the account, debit and credit per book. For document flow (order to fulfillment to invoice), use the link tables rather than createdfrom alone, since one order line can feed several downstream lines.
- Lines only: transactionline.mainline = 'F' AND transactionline.taxline = 'F'
- Header values: join the mainline = 'T' row or read transaction directly
- Names without joins: BUILTIN.DF(column)
Worked example
Open sales order lines with item and customer: SELECT t.tranid, BUILTIN.DF(t.entity) AS customer, i.itemid, tl.quantity FROM transaction t JOIN transactionline tl ON tl.transaction = t.id JOIN item i ON tl.item = i.id WHERE t.type = 'SalesOrd' AND tl.mainline = 'F' AND tl.taxline = 'F' AND tl.isclosed = 'F'. Add JOIN accountingperiod ap ON t.postingperiod = ap.id to filter by period name, or JOIN subsidiary s ON tl.subsidiary = s.id to split by legal entity. Expect quantity to come back negative on sales lines; wrap it in ABS() or multiply by -1 for display.
Version differences and gotchas
NetSuite has two analytics schemas. NetSuite2.com is the one SuiteQL uses and the one Oracle develops; the legacy NetSuite.com ODBC schema has plural table names and different columns, so a query copied from an old ODBC report will fail in SuiteQL. Tables also appear and disappear with features: bom and bomrevision need Advanced BOM, consolidatedexchangerate needs OneWorld, inventorynumber needs lot or serial tracking. A role without permission to a record type gets an error or an empty result, not a warning, which looks like missing data.
How to check the result in the system
Go to Setup > Records Catalog. Search the record, open it and look at the Fields tab for every column name and data type and the Joins tab for every supported join, including the exact join fields. The catalog reflects your account's features and custom fields, so it is the final word for your environment. Then run the query in a SuiteQL tool or through N/query with a ROWNUM filter to check row counts against a saved search on the same criteria.
Frequently Asked Questions
Where can I see the list of tables in NetSuite?
Setup > Records Catalog lists every record type available to SuiteQL in your account, with its fields and joins. It reflects your enabled features and custom records. For the older ODBC schema, Oracle publishes a separate NetSuite.com schema browser, but those names do not work in SuiteQL.
How do I join transaction and transactionline in SuiteQL?
Use transactionline.transaction = transaction.id. Each transaction has one mainline = 'T' row plus item, tax and other lines. Add mainline = 'F' and taxline = 'F' for item lines, or keep mainline = 'T' when you want one row per transaction.
Which table has the GL account for a transaction?
transactionaccountingline. Join it with transaction = transactionline.transaction and transactionline = transactionline.id, then join account on account.id. Filter posting = 'T' and pick one accountingbook if Multi-Book Accounting is enabled, or totals will double.
Are NetSuite ODBC tables the same as SuiteQL tables?
Only for the NetSuite2.com data source. If your ODBC connection uses NetSuite2.com, table names match SuiteQL. If it still uses the legacy NetSuite.com data source, names such as transactions and transaction_lines differ and queries need rewriting.
If you want your team to ask NetSuite these questions in plain English, with the SuiteQL shown and roles respected, we can set up a private AI layer over your account.
Related Tools
NetSuite SuiteQL Transaction and TransactionLine Columns Reference
Column-by-column guide to the two SuiteQL tables behind every transaction query, with types, example values and the sign conventions that trip people up.
NetSuiteNetSuite SuiteQL BUILTIN Functions Reference
The ten BUILTIN functions Oracle documents for SuiteQL, with exact parameter order and one-line examples, plus the Oracle SQL functions you will use beside them.
NetSuiteNetSuite SuiteQL Transaction Query Generator
Pick a transaction type, date range and row limit and get ready-to-run SuiteQL for headers, lines and the REST call that executes it.
NetSuiteNetSuite Transaction Type Codes Reference
Look up the short type code stored in transaction.type for common NetSuite transactions, with the matching record id.
More NetSuite tools
See all- NetSuite Saved Search Formula Generator
- NetSuite CSV Import Template Guide
- NetSuite Role Permission Matrix Worksheet
- NetSuite Inventory Costing Method Reference
- NetSuite Month End Close Checklist
- NetSuite License Cost Estimator
- NetSuite User Access Request Form Template
- NetSuite Custom Record Design Spec Template
- NetSuite SuiteScript Deployment Handover Template
Keep exploring
Free tools
- NetSuite SuiteQL Custom List and Custom Record Query GeneratorEnter a custom list or custom record script ID and its fields to get SuiteQL for its values, its records, a join from transactions, and a ready REST call.
- NetSuite SuiteQL Transaction and TransactionLine Columns ReferenceColumn-by-column guide to the two SuiteQL tables behind every transaction query, with types, example values and the sign conventions that trip people up.
- SuiteQL Query GeneratorFill in a table, columns, filter and sort and get a SuiteQL statement ready for the N/query module, the REST SuiteQL endpoint or the SuiteAnalytics Workbook.
ERP fixes and how-tos
- How to write and run SuiteQL queries in NetSuiteRun SuiteQL from Analytics > SuiteQL Query Tool or via N/query in SuiteScript. Syntax basics, joins, pagination and real examples for NetSuite reporting.
- How to use formula fields in a NetSuite saved searchHow to build formula (text), formula (numeric) and formula (date) fields in a NetSuite saved search, with CASE WHEN, DECODE and NVL examples that actually work.
- N/query versus N/search: choosing the right SuiteScript data access moduleCompare NetSuite's N/query (SuiteQL) and N/search modules for performance, governance cost and capability, and decide which to use for joins, aggregation, sublist reads and large exports in SuiteScript 2.1.
AI on your ERP
- AI agents for NetSuite manufacturing operationsAI agents for NetSuite manufacturing: WIP tracking, routing exceptions, and work order status grounded in SuiteQL, with human approval on anything that writes back.
- AI for NetSuite, Beyond the Built-In Text ToolsNetSuite's built-in AI covers text generation, not grounded answers on your own data. See how a private LLM over SuiteQL adds real Q&A and controls.
Integrations
- How to add AI agents to Oracle NetSuiteHow to add AI agents to Oracle NetSuite: SuiteQL and REST tool layer, RESTlet writes, role-scoped tokens, human approval, and where a private model fits.
- How to use Claude with Oracle NetSuiteUse Claude with Oracle NetSuite through MCP connectors, the Anthropic API, AWS Bedrock or Vertex, plus the on-prem alternative for controlled data.