Querying Data
Filter, sort, and paginate Abrova Sync documents with a fluent query API. Comparison operators, array-contains, IN, plus ordering and offset pagination — across Flutter, React Native, Web, Android, iOS, Node.js, and REST API.
A query is a recipe for narrowing down a collection: "give me users where age is at least 18, ordered by signup date, limit 20". The SDK builds the query on the client, the backend evaluates it, and you get back a result list. Queries also work as inputs to realtime listeners — same fluent shape.
where()
Filter rows by field. Chain multiple where() calls to AND them.
orderBy()
Sort by one field, ascending or descending. A second orderBy replaces the first.
limit() + offset()
Page through large result sets without loading everything.
Available operators#
| Operator | Symbol / Enum | Description |
|---|---|---|
| Equal | == | Field equals value |
| Not Equal | != | Field does not equal value |
| Greater Than | > | Field strictly greater than value (numeric fields only) |
| Greater Than or Equal | >= | Field greater than or equal to value (numeric fields only) |
| Less Than | < | Field strictly less than value (numeric fields only) |
| Less Than or Equal | <= | Field less than or equal to value (numeric fields only) |
| Array Contains | array-contains | Array field contains the value (matched as text, so a partial match also counts) |
| In | in | Field value is in the given array |
| Not In | not-in | Not supported: the filter is ignored |
Good to know: two where() calls on the same field with different operators make a range (age >= 18 and age < 65); the same operator twice on one field is refused. Sorting puts numbers in number order, then text, and documents without the field last (createdAt / updatedAt sort by the document timestamps). A query returns at most 1000 documents (100 when you set no limit).
Queries and your security rules#
A query must match your security rules: it is allowed only when the rules allow every document it could return, otherwise it fails as a whole with 403 query_not_allowed. The error names the fields the rules read — filter on them with == (or in). For example, with a rule data.ownerId === auth.uid, query where('ownerId', '==', <the user's id>). Every SDK reports it as a typed error with those fields, from get() and from listeners.
When the rules allow a query because of its == / in filters, those filters compare by type: 5 matches 5, not "5". Send values with their real type.
A query on a collection that has no documents yet is checked against the rules like any other query. When the rules allow it, it returns an empty list. When they don't, it fails with query_not_allowed, even though there is nothing to return.
The errors and how to catch them on each platform are in Platform Notes.
Flutter#
// Build queries with fluent API
final adults = await users
.query()
.where('age', QueryOperator.greaterThanOrEqual, 18)
.where('status', QueryOperator.equal, 'active')
.orderBy('createdAt', direction: OrderDirection.descending)
.limit(20)
.get();
// Available operators
QueryOperator.equal // ==
QueryOperator.notEqual // !=
QueryOperator.greaterThan // >
QueryOperator.greaterThanOrEqual // >=
QueryOperator.lessThan // <
QueryOperator.lessThanOrEqual // <=
QueryOperator.arrayContains // array contains value
QueryOperator.isIn // value in array
QueryOperator.notIn // value not in array (not supported)React Native#
// Build queries with fluent API
const adults = await users
.query()
.where('age', '>=', 18)
.where('status', '==', 'active')
.orderBy('createdAt', 'desc')
.limit(20)
.get();
// Available operators: '==', '!=', '>', '>=', '<', '<=', 'array-contains', 'in', 'not-in'Web#
// Build queries with fluent API
const adults = await users
.where('age', '>=', 18)
.where('status', '==', 'active')
.orderBy('createdAt', 'desc')
.limit(20)
.get();
// Available operators: '==', '!=', '>', '>=', '<', '<=', 'array-contains', 'in', 'not-in'Android#
// Build queries with fluent API
lifecycleScope.launch {
val adults = users
.query()
.where("age", QueryOperator.GREATER_THAN_OR_EQUAL, 18)
.where("status", QueryOperator.EQUAL, "active")
.orderBy("createdAt", OrderDirection.DESCENDING)
.limit(20)
.get()
}
// Available operators
QueryOperator.EQUAL // ==
QueryOperator.NOT_EQUAL // !=
QueryOperator.GREATER_THAN // >
QueryOperator.GREATER_THAN_OR_EQUAL // >=
QueryOperator.LESS_THAN // <
QueryOperator.LESS_THAN_OR_EQUAL // <=
QueryOperator.ARRAY_CONTAINS // array contains
QueryOperator.IN // value in array
QueryOperator.NOT_IN // value not in array (not supported)iOS#
// Build queries with fluent API
Task {
let adults = try await users
.query()
.where("age", .greaterThanOrEqual, 18)
.where("status", .equal, "active")
.orderBy("createdAt", direction: .descending)
.limit(20)
.get()
}
// Available operators: .equal, .notEqual, .greaterThan, .greaterThanOrEqual,
// .lessThan, .lessThanOrEqual, .arrayContains, .in, .notInNode.js#
// Build queries with fluent API
const adults = await users
.where('age', '>=', 18)
.where('status', '==', 'active')
.orderBy('createdAt', 'desc')
.limit(20)
.get();
// Compound queries
const recentActiveUsers = await users
.where('status', '==', 'active')
.where('lastLogin', '>=', thirtyDaysAgo)
.orderBy('lastLogin', 'desc')
.get();
// Pagination with offset
const page1 = await users.orderBy('name').limit(10).get();
const page2 = await users.orderBy('name').limit(10).offset(10).get();
const page3 = await users.orderBy('name').limit(10).offset(20).get();REST API#
# Query documents: POST the same shape the SDKs send
curl -X POST "https://sync.abrova.ir/databases/{databaseName}/collections/{collectionName}/documents/sdk/query" \
-H "x-api-key: YOUR_PROJECT_API_KEY" \
-H "x-server-secret: YOUR_SERVER_SECRET" \
-H "Content-Type: application/json" \
-d '{
"filters": [
{ "field": "age", "operator": ">=", "value": 18 },
{ "field": "status", "operator": "==", "value": "active" }
],
"orderBy": { "field": "createdAt", "direction": "desc" },
"limit": 20,
"offset": 40
}'
# Response: { "data": [...], "total": 123 }
# Operators: ==, !=, >, >=, <, <=, array-contains, in (not-in is not supported)
# Or GET with a JSON filter and sort (MongoDB-style operators)
curl -G "https://sync.abrova.ir/databases/{databaseName}/collections/{collectionName}/documents/sdk" \
--data-urlencode 'filter={"age":{"$gte":18},"status":"active"}' \
--data-urlencode 'sort={"createdAt":-1}' \
--data-urlencode 'limit=20' \
--data-urlencode 'skip=40' \
-H "x-api-key: YOUR_PROJECT_API_KEY" \
-H "x-server-secret: YOUR_SERVER_SECRET"
# Response: { "data": [...], "total": 123, "skip": 40, "limit": 20 }
# Filter operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $containsNext: keep a UI in sync with Realtime Listeners.