Querying
Use the Prisma-like API to query your NetSuite data.
SuitePortal provides a Prisma-like query API that compiles to SuiteQL under the hood.
Creating a Client
import { createClient } from './.suiteportal/client';
const ns = await createClient({
accountId: process.env.NETSUITE_ACCOUNT_ID!,
consumerKey: process.env.NETSUITE_CONSUMER_KEY!,
consumerSecret: process.env.NETSUITE_CONSUMER_SECRET!,
tokenId: process.env.NETSUITE_TOKEN_ID!,
tokenSecret: process.env.NETSUITE_TOKEN_SECRET!,
});findMany
Fetch multiple records:
const customers = await ns.customer.findMany({
select: { id: true, companyname: true, email: true },
where: { isinactive: false },
orderBy: { companyname: 'asc' },
take: 50,
skip: 0,
});SELECT id, companyname, email
FROM customer
WHERE isinactive = 'F'
ORDER BY companyname ASC
-- limit=50, offset=0 (REST params)findFirst
Fetch a single record (returns null if not found):
const customer = await ns.customer.findFirst({
where: { companyname: { contains: 'Acme' } },
});findFirst is a convenience wrapper — it calls findMany with take: 1 and returns the first result or null.
count
Count matching records:
const total = await ns.customer.count({
where: { isinactive: false },
});
// Returns: 1102SELECT COUNT(*) AS count
FROM customer
WHERE isinactive = 'F'Where Filters
Direct value (equals shorthand)
where: { companyname: 'Acme Corp' }
// → companyname = 'Acme Corp'Comparison operators
Prop
Type
where: {
id: { gt: 100 },
id: { gte: 100 },
id: { lt: 200 },
id: { lte: 200 },
}String matching
Prop
Type
where: {
companyname: { contains: 'Acme' }, // LIKE '%Acme%'
companyname: { startsWith: 'Acme' }, // LIKE 'Acme%'
companyname: { endsWith: 'Corp' }, // LIKE '%Corp'
}IN / NOT IN
where: {
status: { in: ['open', 'pending'] },
status: { notIn: ['closed'] },
}Null checks
where: {
email: { isNull: true }, // email IS NULL
email: { isNull: false }, // email IS NOT NULL
}Logical combinators
// All conditions must match
where: {
AND: [
{ isinactive: false },
{ email: { isNull: false } },
],
}// Any condition matches
where: {
OR: [
{ companyname: { contains: 'Acme' } },
{ companyname: { contains: 'Globex' } },
],
}// Negate a condition
where: {
NOT: { isinactive: true },
}// Combine all three
where: {
AND: [
{ isinactive: false },
{
OR: [
{ companyname: { startsWith: 'A' } },
{ companyname: { startsWith: 'B' } },
],
},
],
}Select
Choose which fields to return. id is always included automatically.
select: { companyname: true, email: true, phone: true }
// → SELECT id, companyname, email, phoneOmitting select returns all scalar fields defined in the schema.
OrderBy
Sort results by one or more fields:
orderBy: { companyname: 'asc' }
orderBy: { companyname: 'asc', id: 'desc' }Include (Relations)
Load related records in a single query using include:
const orders = await ns.salesorder.findMany({
select: { id: true, tranid: true },
include: { entity: true },
take: 10,
});
// → [{ id: 1, tranid: 'SO-001', entity: { id: 42, companyname: 'Acme' } }]You can also select specific fields on the included relation:
include: {
entity: { select: { companyname: true, email: true } },
}Only many-to-one relations are supported. See Relations & Include for full details.
Pagination
take: 50, // Limit (default: 1000)
skip: 100, // Offset (default: 0)take and skip map to REST limit and offset query parameters on the SuiteQL endpoint — they are not embedded in the SQL query itself. NetSuite caps results at 1,000 rows per request.