Guides
Query rows
Filter, sort and page through records with the same grammar on every client. Dates, time variables and what the hub assumed.
query() reads one kind, a page at a time. It is the same on the app client (as the signed-in person) and the server client (as your account or key).
const { rows, next, applied } = await db.query('job', {
where: { status: { in: ['open', 'quoted'] }, created_at: { gte: '$MONTH_BEGIN' } },
orderBy: ['created_at', 'desc'],
select: ['id', 'title', 'status'],
limit: 50,
});Filters
{ column: value } means equals; a list means any of them (on a list column, such as tags, it means all of them). { column: { op: value } } uses an operator:
| Operators | For |
|---|---|
eq, neq, in, nin, isNull |
Any column |
gt, gte, lt, lte |
Numbers, dates and times |
contains, startsWith, like, ilike |
Text |
has, hasAny, hasAll, isEmpty |
Lists, such as tags or several choices |
Combine with and: [...], or: [...] and not: {...}:
await db.query('contact', {
where: { or: [{ tags: 'VIP' }, { suburb: 'Carindale' }], not: { email: { isNull: true } } },
});A link reads as <link>_id: jobs for one customer are { customer_id: contactId }.
Dates and time variables
A date such as '2026-09-30' compared with a timestamp means that whole day in your time zone (Australia/Sydney unless you pass tz). Time variables save working dates out:
| Variable | Means |
|---|---|
$TODAY, $DAY_BEGIN |
The start of today |
$WEEK_BEGIN, $MONTH_BEGIN, $QUARTER_BEGIN, $YEAR_BEGIN |
The start of this week, month, quarter, year |
$FY_BEGIN |
The start of this financial year (1 July) |
$NOW |
This moment |
They take an offset in their own unit: $MONTH_BEGIN-1 is the start of last month, $TODAY-7 a week ago.
Order and pages
- One sort column, and
idbreaks ties. The default iscreated_at, newest first. limitis 1 to 1,000 rows, 50 by default.- Pass
nextback asafterfor the following page, with the sameorderBy. It isnullon the last page. queryAll()walks every page for you, stopping atmaxRows(10,000 by default) so a whole list is never read by accident.
for await (const person of db.queryAll('contact', { where: { tags: 'VIP' }, select: ['first_name', 'email'] })) {
render(person.first_name, person.email);
}One record
const one = await app.get('job', job.id); // null when there is none this caller may readCounts and totals
For numbers rather than rows (how many people joined each month, the total of a number field), db.aggregate() asks the hub to do the counting. No person's details come back, only the numbers.
const joined = await db.aggregate({
measures: [{ column: 'people', agg: 'count' }],
time: { grain: 'month', preset: '12m' },
});people counts people; any number field you've named under Contacts, Your fields can be summed or averaged. A range preset is one of 7d, 30d, 90d, 12m, mtd, qtd, ytd, fytd, this_month, last_month, this_fy or last_fy, or give from and to dates. It needs the account's token with Contacts read access.
What the hub assumed
applied says what the query actually used: the order, the limit, the time zone and the date each time variable meant. Say it back when a person asked in their own words. On the server client, hidden also counts the columns left out (see Kinds and records).