Query Syntax Cheat Sheet One-page reference for ObjectStack QuerySchema — filters, sorts, pagination, aggregations, and joins
Quick reference for building queries with the ObjectStack QuerySchema.
Source: packages/spec/src/data/query.zod.ts and packages/spec/src/data/filter.zod.ts
Import: import { QuerySchema, FilterConditionSchema } from '@objectstack/spec/data'
const query = {
object: 'task' ,
fields: [ 'id' , 'title' , 'status' , 'assigned_to' ],
where: { /* filters */ },
orderBy: [{ field: 'created_at' , order: 'desc' }],
limit: 20 ,
offset: 0
};
Operator Description Example $eqEqual to { status: { $eq: 'open' } }$neNot equal to { status: { $ne: 'closed' } }$gtGreater than { amount: { $gt: 100 } }$gteGreater than or equal { amount: { $gte: 100 } }$ltLess than { amount: { $lt: 1000 } }$lteLess than or equal { amount: { $lte: 1000 } }
Operator Description Example $inValue is in array { status: { $in: ['open', 'pending'] } }$ninValue is NOT in array { status: { $nin: ['closed', 'cancelled'] } }
Operator Description Example $betweenValue is in range (inclusive) { price: { $between: [10, 100] } }
Operator Description Example $containsString contains substring { title: { $contains: 'urgent' } }$startsWithString starts with { email: { $startsWith: 'admin' } }$endsWithString ends with { email: { $endsWith: '@acme.com' } }
Operator Description Example $nullValue is null { deleted_at: { $null: true } }$existsField has a value { phone: { $exists: true } }
{
where : {
$and : [
{ status: { $eq: 'open' } },
{ priority: { $in: [ 'high' , 'critical' ] } }
]
}
}
{
where : {
$or : [
{ status: { $eq: 'open' } },
{ status: { $eq: 'pending' } }
]
}
}
{
where : {
$not : { status : { $eq : 'closed' } }
}
}
{
where : {
$and : [
{ type: { $eq: 'bug' } },
{
$or: [
{ priority: { $eq: 'critical' } },
{ assigned_to: { $null: true } }
]
}
]
}
}
Sort results with orderBy (array of sort nodes):
{
orderBy : [
{ field: 'priority' , order: 'desc' },
{ field: 'created_at' , order: 'asc' }
]
}
Property Type Description fieldstringField name to sort by order'asc' | 'desc'Sort direction
{
limit : 20 , // Records per page (max varies by config)
offset : 40 // Skip first 40 records (page 3)
}
{
limit : 20 ,
keyset : {
after : 'eyJpZCI6MTAwfQ==' , // Cursor from previous response
field : 'id' ,
order : 'asc'
}
}
{
fields : [ 'id' , 'title' , 'status' , 'created_at' ]
}
{
fields : [
'id' ,
'title' ,
{ field: 'assigned_to' , alias: 'assignee' }
]
}
Function Description Example countCount records { function: 'count', alias: 'total' }sumSum numeric field { function: 'sum', field: 'amount', alias: 'total_amount' }avgAverage numeric field { function: 'avg', field: 'rating', alias: 'avg_rating' }minMinimum value { function: 'min', field: 'price', alias: 'min_price' }maxMaximum value { function: 'max', field: 'price', alias: 'max_price' }count_distinctCount unique values { function: 'count_distinct', field: 'category', alias: 'categories' }array_aggCollect into array { function: 'array_agg', field: 'tag', alias: 'all_tags' }string_aggConcatenate strings { function: 'string_agg', field: 'name', alias: 'names' }
{
object : 'order' ,
aggregations : [
{ function: 'count' , alias: 'order_count' },
{ function: 'sum' , field: 'total' , alias: 'revenue' },
{ function: 'avg' , field: 'total' , alias: 'avg_order' }
],
groupBy : [ 'status' ],
having : { order_count : { $gt : 10 } }
}
Type Description innerRecords with matches in both objects leftAll records from left + matching from right rightAll records from right + matching from left fullAll records from both objects
Strategy Description autoLet the engine choose the best strategy databasePush join to the database level hashIn-memory hash join loopNested loop join
{
object : 'order' ,
fields : [ 'id' , 'total' , 'customer.name' , 'customer.email' ],
joins : [
{
type: 'inner' ,
object: 'customer' ,
on: { 'order.customer_id' : 'customer.id' },
strategy: 'auto'
}
]
}
{
object : 'article' ,
fullTextSearch : {
query : 'kubernetes deployment' ,
fields : [ 'title' , 'body' , 'tags' ],
fuzzy : true ,
operator : 'and' ,
boost : { title : 2.0 , body : 1.0 },
minScore : 0.5 ,
language : 'english' ,
highlight : true
}
}
Property Type Description querystringSearch text fieldsstring[]Fields to search (optional — defaults to all searchable) fuzzybooleanEnable fuzzy matching for typo tolerance operator'and' | 'or'How to combine search terms boostRecord<string, number>Field relevance weights minScorenumberMinimum relevance score (0–1) languagestringLanguage for stemming/stopwords highlightbooleanReturn highlighted matches
Function Description row_numberSequential row number within partition rankRank with gaps for ties dense_rankRank without gaps for ties percent_rankRelative rank as percentage lagAccess previous row value leadAccess next row value first_valueFirst value in window last_valueLast value in window sum / avg / count / min / maxRunning aggregations
{
object : 'employee' ,
fields : [ 'name' , 'department' , 'salary' ],
windowFunctions : [
{
function: 'rank' ,
alias: 'salary_rank' ,
partitionBy: [ 'department' ],
orderBy: [{ field: 'salary' , order: 'desc' }]
}
]
}
{
object : 'task' ,
fields : [ 'category' ],
distinct : true
}
{
object : 'order' ,
fields : [ 'customer_id' ],
aggregations : [
{ function: 'sum' , field: 'total' , alias: 'total_spent' }
],
groupBy : [ 'customer_id' ],
having : { total_spent : { $gt : 1000 } }
}
{
object : 'task' ,
fields : [ 'id' , 'title' , 'status' , 'assigned_to' , 'created_at' ],
where : { status : { $ne : 'archived' } },
orderBy : [{ field: 'created_at' , order: 'desc' }],
limit : 25 ,
offset : 0
}
{
object : 'contact' ,
fullTextSearch : { query : 'john' , fields : [ 'first_name' , 'last_name' , 'email' ] },
where : { is_active : { $eq : true } },
orderBy : [{ field: 'last_name' , order: 'asc' }],
limit : 50
}
{
object : 'deal' ,
aggregations : [
{ function: 'count' , alias: 'deal_count' },
{ function: 'sum' , field: 'amount' , alias: 'pipeline_value' },
{ function: 'avg' , field: 'amount' , alias: 'avg_deal_size' }
],
groupBy : [ 'stage' ],
where : { closed_at : { $null : true } }
}
{
object : 'activity' ,
fields : [ 'id' , 'type' , 'description' , 'user.name' , 'created_at' ],
where : {
created_at : { $gte : '2026-01-01T00:00:00Z' }
},
orderBy : [{ field: 'created_at' , order: 'desc' }],
limit : 10
}