Introspection
How SuitePortal discovers your NetSuite schema.
The introspection pipeline connects to your NetSuite account and builds a complete picture of your data model.
What It Does
The pipeline runs in 8 steps:
- Fetch catalog index —
GET /services/rest/record/v1/metadata-catalogreturns all available record types - Fetch record metadata — For each record type, fetches detailed field schemas (types, labels, enums, references)
- Fetch custom record types — SuiteQL query against
customrecordtypeto discover custom records - Fetch custom fields — SuiteQL query against
customfieldforcustbody_,custentity_,custitem_fields - Normalize — Merges REST catalog and SuiteQL data into a unified
NormalizedSchema - Probe field queryability — Tests which fields are actually usable in SuiteQL queries (see below)
- Infer relations — Detects foreign key relationships from
$refreferences and naming conventions - Write output — Saves
schema.json,relations.json, andraw-metadata.json
Running
npx suiteportal introspectField Probing
Not every field in the REST catalog can be used in a SuiteQL SELECT statement. Some fields (like balance, consolbalance, or certain computed fields) exist in the metadata but will return HTTP 400 if you try to query them.
SuitePortal automatically detects these fields during introspection. For each record type, the prober:
- Attempts a
SELECTwith all non-custom fields - If the query succeeds, all fields are queryable — done
- If it returns 400, runs a binary search to isolate the non-queryable fields
- Marks them as
queryable: falsein the schema
Fields marked queryable: false are excluded from findMany/findFirst SELECT statements by default, preventing runtime errors.
Optimizations
The prober uses three strategies to stay fast:
- Table deduplication — Transaction subtypes (salesorder, invoice, purchaseorder, etc.) all share the same
transactiontable. SuitePortal probes the table once and applies results to all subtypes. This reduces 18+ HTTP calls to 1. - Parallel probing — Up to 5 unique tables are probed concurrently.
- Parallel binary search — When a batch fails, both halves are tested simultaneously.
Terminal Output
During introspection, the CLI shows a live spinner with per-record progress:
→ Probing SuiteQL field queryability...
✓ customer — all queryable
✓ transaction (salesorder, invoice, ...) — 5 fields excluded
✓ item — 2 fields excluded
✓ Field probing complete (8 records, 7 non-queryable fields)Schema Output
Non-queryable fields include a queryable: false property:
{
"balance": {
"id": "balance",
"label": "Balance",
"type": "currency",
"required": false,
"readOnly": true,
"nativeType": "number",
"queryable": false
}
}Fields without the queryable property (or queryable: true) are safe for SuiteQL.
Filtering Records
By default, SuitePortal introspects all record types. To limit to specific records:
export default {
recordTypes: ['customer', 'salesorder', 'invoice'],
};Output: schema.json
The normalized schema is the single source of truth for both the generator and the runtime:
{
"generatedAt": "2025-01-15T12:00:00Z",
"accountId": "1234567_SB1",
"records": {
"customer": {
"id": "customer",
"label": "Customer",
"isCustom": false,
"fields": {
"id": {
"id": "id",
"label": "Internal ID",
"type": "integer",
"required": true,
"readOnly": true,
"nativeType": "integer"
},
"companyname": {
"id": "companyname",
"label": "Company Name",
"type": "string",
"required": false,
"readOnly": false,
"nativeType": "string"
}
},
"relations": {},
"sublists": {}
}
}
}Field Types
SuitePortal normalizes NetSuite's various field types into a consistent set:
| Normalized Type | NetSuite Sources |
|---|---|
string | Free-form text, textarea |
text | Long text fields |
richtext | HTML rich text |
email | Email addresses |
url | URL/hyperlinks |
phone | Phone numbers |
integer | Integer numbers |
float | Decimal numbers |
currency | Money amounts |
percent | Percentage values |
boolean | Checkbox fields |
date | Date-only fields |
datetime | Date + time fields |
select | List/Record (single select) |
multiselect | Multiple select |
unknown | Unrecognized types |