Error messages, decoded

How do I list the tables and columns in a NetSuite account from SuiteQL?

Updated
suiteqlerrorsmetadatacatalogdiscovery

Short answer

You cannot. OA_TABLES and OA_COLUMNS are rejected with "Invalid search type", and ALL_TABLES, USER_TAB_COLUMNS and ALL_TAB_COLUMNS are absent too. For columns, run SELECT * with FETCH FIRST 1 ROWS ONLY; for custom records, query customrecordtype.

You cannot, not from SuiteQL itself. Every standard catalog view tried here was rejected. Discover columns with SELECT * limited to one row, and discover custom record types by querying customrecordtype. Everything else has to come from the Records Browser.

Minimal reproduction

SELECT * FROM OA_TABLES FETCH FIRST 20 ROWS ONLY
N/query.runSuiteQL error: Invalid search type: OA_TABLES

OA_COLUMNS gives Invalid search type: OA_COLUMNS. ALL_TABLES, USER_TAB_COLUMNS and ALL_TAB_COLUMNS each give the same message with their own name. Adding a WHERE clause changes nothing: SELECT * FROM OA_TABLES WHERE table_name LIKE '%BUILTIN%' returned the error byte for byte identical to the bare form.

DUAL is the one Oracle-style oddity that survives: SELECT * FROM DUAL returns a single row with a single column dummy set to "X".

The fix: enumerate columns one table at a time

SELECT * FROM transaction FETCH FIRST 1 ROWS ONLY

The returned object's key set is the column list for that row. Columns that are null on that row come back as explicit keys with a null value rather than being omitted, which is the reason one row is worth anything at all. On the ten tables checked this way, the single-row key set matched the union of keys over 20 rows every time. The run records that as an observation and not as a schema guarantee, so on a wide, sparsely populated table, treat the one-row list as a strong lead rather than a certified schema.

Two things that output makes obvious and no documentation will: custom fields are interleaved with standard ones with no special syntax and no separate catalog lookup, and roughly two thirds of the columns on transaction in a mature account were account-specific custom fields. Any column reference you copy from someone else's account is a guess.

A wrong guess does not announce itself either. With a FETCH FIRST clause in the statement, an unresolvable column is most often reported as syntax error, state:0(10102) near: FETCH(1,NN, token code:0), blaming a clause that is fine. It is not the only outcome: the same shape returned a bare Unexpected Error in one run. So follow the action rather than the diagnosis: if SuiteQL blames your FETCH clause, delete the FETCH clause and run again, because the second error names the real problem. See the FETCH page.

A custom field found this way is queryable directly by its field id, with no join. Substitute a field id from your own SELECT * output for the one the run happened to pick:

SELECT custbody1 FROM transaction FETCH FIRST 1 ROWS ONLY

The fix: enumerate custom record types

SELECT scriptid, internalid FROM customrecordtype FETCH FIRST 25 ROWS ONLY

customrecordtype is directly queryable. Take a scriptid from that result and use it as a table name in lowercase, for example SELECT * FROM customrecord_agile_sprint FETCH FIRST 1 ROWS ONLY on the record type the run happened to pick, and combine it with the one-row trick above to get that record's columns.

Take the whole list in one call, because there is no second page to fetch. SQL OFFSET is silently discarded here. SELECT id FROM transaction ORDER BY id OFFSET 500 ROWS FETCH NEXT 5 ROWS ONLY returned the same five rows as the same statement with OFFSET 0, no error, no warning. Through N/query.runSuiteQL there is no URL parameter to fall back on, so raise the FETCH FIRST number rather than trying to page.

Why this happens

Observed, not inferred: five catalog view names were tried and all five were rejected with Invalid search type, while ordinary tables in the same session resolved normally. The error text names the view as a search type, which is consistent with SuiteQL resolving FROM targets against NetSuite record and search types rather than against a SQL data dictionary.

Where inference begins: this does not prove no catalog exists anywhere in NetSuite, only that these five names are unreachable from this surface. The same gap kills any attempt to enumerate the BUILTIN functions, since the only route tried was OA_TABLES.

How to tell if you are affected

Run the reproduction in your own account. It takes seconds and the answer is unambiguous.

SELECT * FROM OA_TABLES FETCH FIRST 20 ROWS ONLY

If it returns Invalid search type: OA_TABLES, you are on the same surface as this run, where the other four names fail the same way. If it returns rows, your account exposes something this one did not, and that is worth writing down.

Scope: one production account on NetSuite 2026.1. Everything quoted here ran through N/query.runSuiteQL, and none of the five catalog views was tried on the REST endpoint, so this page says nothing about how that surface answers. The error strings above are NetSuite's own text verbatim.

How this was established

15 probe runs against one live NetSuite 2026.1 account on the Administrator role, through N/query.runSuiteQL. Where this page draws a boundary around a finding, that boundary is the edge of what was actually run.

Related

From the blog