Deploy your first customer portal →
Open Source

NetSuite's typed layer for
AI agents, APIs, and portals.

An open-source foundation for modern NetSuite apps. Give your agents and portals full context over every record, field, and relation.

Get Started
where: {
isInactive: { equals: false },
balance: { gt: 1000 },
}, select: {
companyName: true,
email: true,
balance: true,
}, orderBy: { balance: 'desc' },
take: 50,
});
// customers: Customer[] — fully typed, auto-paginated

Built for NetSuite developers

Everything you need to query NetSuite with confidence.

Type-Safe Queries

Every field typed. Every query validated against your schema at compile time.

Schema Introspection

Auto-discover 142+ record types and 1,200+ fields from your NetSuite instance.

Code Generation

Generate TypeScript interfaces and a fully typed client from your schema.

Raw SuiteQL

Drop down to raw queries for JOINs, aggregations, and complex logic.

OAuth Built-In

HMAC-SHA256 signing, token auth, retry logic, and rate limiting included.

Smart Pagination

Automatic cursor-based pagination. Fetch 10 or 10,000 records seamlessly.

Three commands to type safety

From zero to fully-typed NetSuite queries in under five minutes.

1

Introspect

Connect to your NetSuite instance and discover every record type, field, and relation.

✓ Connected to NetSuite (1234567_SB1)
✓ Fetched 142 record types
✓ Discovered 1,229 fields
✓ Inferred 6 relations
✓ Wrote .suiteportal/schema.json
2

Generate

Read the schema and emit TypeScript interfaces and a typed client wrapper.

✓ Read schema (142 records, 1,229 fields)
✓ Generated types.ts
✓ Generated client.ts
✓ Generated index.ts
→ .suiteportal/client/ ready
3

Query

Use the Prisma-like API with full autocomplete and type safety.

app.ts
const orders = await ns.salesorder.findMany({
  where: { entity: { equals: 42 } },
  orderBy: { trandate: 'desc' },
  take: 25,
});

const count = await ns.customer.count();
// count: number

See the difference

30+ lines of OAuth boilerplate and raw SQL versus six lines of typed queries.

Without SuitePortal
api/customers.ts
// Build OAuth 1.0a signature manually
const timestamp = Math.floor(Date.now() / 1000);
const nonce = crypto.randomUUID();
const baseUrl = `https://${accountId
  .replace('_', '-').toLowerCase()}.suitetalk.api.netsuite.com`;

const signature = computeHmacSha256(
  'POST', baseUrl, consumerKey,
  consumerSecret, tokenId, tokenSecret,
  timestamp, nonce,
);

// Construct Authorization header
const auth = 'OAuth '
  + `realm="${accountId}",`
  + `oauth_consumer_key="${consumerKey}",`
  + `oauth_token="${tokenId}",`
  + `oauth_signature="${signature}",`
  + `oauth_timestamp="${timestamp}",`
  + `oauth_nonce="${nonce}",`
  + 'oauth_signature_method="HMAC-SHA256",'
  + 'oauth_version="1.0"';

// Make the request
const res = await fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Prefer': 'transient',
    'Authorization': auth,
  },
  body: JSON.stringify({
    q: 'SELECT id, companyname, email FROM customer',
  }),
});

const data: any = await res.json();
//          ^^^ no types, no autocomplete
With SuitePortal
query.ts
const customers = await ns.customer.findMany({
  where: { isInactive: { equals: false } },
  select: {
    companyName: true,
    email: true,
  },
  orderBy: { companyName: 'asc' },
});

// customers: Customer[] — fully typed

That's 35 lines gone.

No OAuth signing. No header construction. No raw SQL strings. Just a typed, autocompletable API.

Full CRUD operations

Create, update, delete, and upsert records with the same typed, autocompletable API. Every mutation validates against your schema and goes through the REST Record API with built-in retry logic.

Upsert by external ID

Sync records from external systems using upsert with externalId. Creates if missing, updates if found.

mutations.ts
// Create a new customer
const customer = await ns.customer.create({
  data: {
    companyName: 'Acme Corp',
    email: 'info@acme.com',
  },
});

// Update by ID
await ns.customer.update({
  where: { id: { equals: 42 } },
  data: { email: 'new@acme.com' },
});

// Delete by ID
await ns.customer.delete({
  where: { id: { equals: 42 } },
});

// Upsert — create or update by externalId
await ns.customer.upsert({
  where: { externalId: 'SF-1234' },
  data: { companyName: 'Acme Corp' },
});

Relations made easy

Use include to follow many-to-one relations. SuitePortal generates LEFT JOINs automatically.

query.ts
const orders = await ns.salesorder.findMany({
  select: {
    tranId: true,
    total: true,
  },
  include: {
    entity: {
      select: {
        companyName: true,
        email: true,
      },
    },
  },
  take: 10,
});
result
[
  {
    "tranId": "SO-1042",
    "total": 2499.99,
    "entity": {
      "companyName": "Acme Corp",
      "email": "info@acme.com"
    }
  },
  // ...more orders
]

Nested, not flat

Related records are nested objects, not flat columns. SuitePortal handles the JOIN and reshapes the rows automatically.

Studio — your NetSuite dashboard

A local web UI for exploring your NetSuite data. Browse records, run queries, and edit fields — without writing a single line of code.

  • Browse every record type with column chooser
  • Filter and sort with a visual query builder
  • Inline edit fields and save changes
  • Create new records with validated forms
  • Drop into raw SuiteQL with the query editor
✓ Loaded schema (142 records)
✓ Connected to NetSuite (1234567_SB1)
Studio running at http://localhost:3000
Press Ctrl+C to stop
// Browse records, run queries, edit inline
SuitePortal Studio — browse customer records with SuiteQL query editor

Start with a template

Scaffold a fully wired project in seconds. Pick your stack and start building.

Next.js

App Router, Server Components, and server actions — ready for production.

ReactApp RouterTypeScript

Express

Lightweight REST API server with typed route handlers and middleware.

Node.jsREST APITypeScript

Vite + React

Fast SPA with React and Vite for internal tools and dashboards.

ReactViteTypeScript
✓ Created suiteportal.config.ts
✓ Created .env with credential placeholders
✓ Installed @suiteportal/cli
✓ Ready — run `npx suiteportal introspect` next

Ready to modernize your NetSuite queries?

Install SuitePortal and go from raw SuiteQL to typed queries and mutations in under five minutes.