Deploy your first customer portal →
SuitePortal/NetSuite ORM

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

setup.ts
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: 1102
SELECT 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, phone

Omitting 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.

On this page