What comes back

Why is my quoted column alias lowercased in SuiteQL results?

Updated
suiteqlaliasidentifiersjsonsilent-failurerest

Short answer

Returned JSON keys come back lowercased on both engines even when the alias is double-quoted. SELECT id AS "Transaction ID" returns the key "transaction id": the space survives, the capitals do not, so row["Transaction ID"] is undefined.

Because the returned key is folded to lowercase whether or not you quoted the alias. SELECT id AS "Transaction ID" comes back as the key transaction id. The space survives, the capitals do not. row["Transaction ID"] is undefined, and the query itself succeeds.

Minimal reproduction

SELECT id AS "Transaction ID" FROM transaction FETCH FIRST 1 ROWS ONLY

Returned key, literally: transaction id. Dropping the AS keyword changes nothing:

SELECT id "Transaction ID" FROM transaction FETCH FIRST 1 ROWS ONLY

Also transaction id. Both of those were run twice, once on each engine, with the same key coming back each time.

To separate the case from the spacing, an alias with no space in it was run:

SELECT id AS TransactionId FROM transaction FETCH FIRST 1 ROWS ONLY

Key returned: transactionid. So it is the case that is destroyed, not the alias as a whole. That one query was run on the REST endpoint only, and only once.

The fix

Alias in lower snake case and read the same string back:

SELECT id AS transaction_id FROM transaction FETCH FIRST 1 ROWS ONLY

Key returned: transaction_id, underscore intact and no capitals to fold. Written this way, what you type in the SQL and what you index in the result are the same token, so there is nothing to get wrong. If you need a label with capitals for a UI, map it in code after the query, not in the alias.

Why this happens

The dialect folds returned identifiers to lowercase and does it regardless of quoting. That is a departure from Oracle-family SQL, where a double-quoted identifier keeps its case exactly, and it is the reason the trap catches experienced people: quoting is precisely what you would reach for to protect the case.

Case is ignored on the input side too. TRANSACTION, transaction, Transaction and "transaction" all resolved to the same table and returned the same row. The quoted probe there quoted the name in its already lowercase form, so it says nothing about what FROM "TRANSACTION" would do; that was not run.

What the run establishes is the input and output pairs above. That a single identifier-folding rule is responsible for both the table-name behaviour and the alias behaviour is inference, and no probe inspected the engine to confirm it.

How to tell if you are affected

Run one query and print the key rather than the value:

SELECT id AS "MixedCase Alias" FROM transaction FETCH FIRST 1 ROWS ONLY

Then log Object.keys(rows[0]). If you see mixedcase alias, every lookup in your code that spells an alias with a capital letter is returning undefined right now. If you see MixedCase Alias, your engine preserves the case and your bug is somewhere else.

This matters because there is no symptom to search for. The query succeeds, the row arrives, and the field simply reads as empty. In a loosely typed language an undefined value tends to surface far downstream as a blank column, a zero total, or a null written into a target system, none of which point back at the alias. The fastest audit is to grep your codebase for a bracket lookup containing an uppercase letter, for example row[" or item[" followed by a capital, and check each hit against the key the engine actually returns.

Scope

One production account on NetSuite 2026.1, aliases on transaction.id. Three aliases were run on both engines and returned identical keys on each: AS "Transaction ID", the same alias without AS, and AS transaction_id, each run once through N/query.runSuiteQL and once, as the run was recorded, against the REST endpoint. The no-space alias AS TransactionId, the one that isolates case from spacing, was run on REST only. No probe aliased a column of any other table.

How this was established

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

Related