Deploy your first customer portal →
SuitePortal/NetSuite ORM

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:

  1. Fetch catalog indexGET /services/rest/record/v1/metadata-catalog returns all available record types
  2. Fetch record metadata — For each record type, fetches detailed field schemas (types, labels, enums, references)
  3. Fetch custom record types — SuiteQL query against customrecordtype to discover custom records
  4. Fetch custom fields — SuiteQL query against customfield for custbody_, custentity_, custitem_ fields
  5. Normalize — Merges REST catalog and SuiteQL data into a unified NormalizedSchema
  6. Probe field queryability — Tests which fields are actually usable in SuiteQL queries (see below)
  7. Infer relations — Detects foreign key relationships from $ref references and naming conventions
  8. Write output — Saves schema.json, relations.json, and raw-metadata.json

Running

npx suiteportal introspect

Field 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:

  1. Attempts a SELECT with all non-custom fields
  2. If the query succeeds, all fields are queryable — done
  3. If it returns 400, runs a binary search to isolate the non-queryable fields
  4. Marks them as queryable: false in 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 transaction table. 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:

suiteportal.config.ts
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 TypeNetSuite Sources
stringFree-form text, textarea
textLong text fields
richtextHTML rich text
emailEmail addresses
urlURL/hyperlinks
phonePhone numbers
integerInteger numbers
floatDecimal numbers
currencyMoney amounts
percentPercentage values
booleanCheckbox fields
dateDate-only fields
datetimeDate + time fields
selectList/Record (single select)
multiselectMultiple select
unknownUnrecognized types

On this page