An open-source foundation for modern NetSuite apps. Give your agents and portals full context over every record, field, and relation.
where: { isInactive: { equals: false }, balance: { gt: 1000 }, },
select: { companyName: true, email: true, balance: true, },
orderBy: { balance: 'desc' }, take: 50,});// customers: Customer[] — fully typed, auto-paginatedEverything you need to query NetSuite with confidence.
Every field typed. Every query validated against your schema at compile time.
Auto-discover 142+ record types and 1,200+ fields from your NetSuite instance.
Generate TypeScript interfaces and a fully typed client from your schema.
Drop down to raw queries for JOINs, aggregations, and complex logic.
HMAC-SHA256 signing, token auth, retry logic, and rate limiting included.
Automatic cursor-based pagination. Fetch 10 or 10,000 records seamlessly.
From zero to fully-typed NetSuite queries in under five minutes.
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.jsonRead 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/ readyUse the Prisma-like API with full autocomplete and type safety.
const orders = await ns.salesorder.findMany({
where: { entity: { equals: 42 } },
orderBy: { trandate: 'desc' },
take: 25,
});
const count = await ns.customer.count();
// count: number30+ lines of OAuth boilerplate and raw SQL versus six lines of typed queries.
// 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 autocompleteconst customers = await ns.customer.findMany({
where: { isInactive: { equals: false } },
select: {
companyName: true,
email: true,
},
orderBy: { companyName: 'asc' },
});
// customers: Customer[] — fully typedThat's 35 lines gone.
No OAuth signing. No header construction. No raw SQL strings. Just a typed, autocompletable API.
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.
// 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' },
});Use include to follow many-to-one relations. SuitePortal generates LEFT JOINs automatically.
const orders = await ns.salesorder.findMany({
select: {
tranId: true,
total: true,
},
include: {
entity: {
select: {
companyName: true,
email: true,
},
},
},
take: 10,
});[
{
"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.
A local web UI for exploring your NetSuite data. Browse records, run queries, and edit fields — without writing a single line of code.
✓ 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
Scaffold a fully wired project in seconds. Pick your stack and start building.
App Router, Server Components, and server actions — ready for production.
Lightweight REST API server with typed route handlers and middleware.
Fast SPA with React and Vite for internal tools and dashboards.
✓ Created suiteportal.config.ts✓ Created .env with credential placeholders✓ Installed @suiteportal/cli✓ Ready — run `npx suiteportal introspect` nextInstall SuitePortal and go from raw SuiteQL to typed queries and mutations in under five minutes.