Filter Operators
Every query and mutation in the Data API accepts a filter object that decides which rows it touches. Filters are plain JavaScript objects, so they read naturally and compose well. This page lists every operator that exists, how to combine them, the edge cases worth knowing, and the ordering and pagination options that usually travel with a filter.
Bare equality
A bare value is an equality check. This is the form you will use most of the time:
useQuery("tasks", { filter: { done: false } });
useQuery("orders", { filter: { status: "paid", user_email: email } });
Multiple keys in the same object are combined with AND, so the second example matches rows that are both paid and belong to that email.
The operator table
When equality is not enough, wrap the value in an operator object. These are all the operators; nothing else exists.
| Operator | Meaning | Example |
|---|---|---|
$eq | Equals (same as a bare value) | { status: { $eq: "paid" } } |
$ne | Not equal | { status: { $ne: "draft" } } |
$gt | Greater than | { price: { $gt: 100 } } |
$gte | Greater than or equal | { price: { $gte: 100 } } |
$lt | Less than | { stock: { $lt: 5 } } |
$lte | Less than or equal | { stock: { $lte: 5 } } |
$in | Value is in the array | { status: { $in: ["paid", "shipped"] } } |
$nin | Value is not in the array | { status: { $nin: ["cancelled", "refunded"] } } |
$like | Pattern match, case sensitive, % as wildcard | { sku: { $like: "TEE-%" } } |
$ilike | Pattern match, case insensitive | { title: { $ilike: "%invoice%" } } |
$is | Matches null or a boolean exactly | { archived_at: { $is: null } } |
$or | Any of an array of filters matches | { $or: [{ done: true }, { priority: "high" }] } |
$and | All of an array of filters match | { $and: [{ price: { $gte: 10 } }, { price: { $lte: 50 } }] } |
Combining with $or and $and
Keys at the same level are already ANDed, so you only need $and when you want two conditions on the same column or when nesting inside $or. Both take an array of filter objects and can nest:
// urgent = high priority, OR overdue and not done
useQuery("tasks", {
filter: {
$or: [
{ priority: "high" },
{
$and: [
{ due_date: { $lt: today } },
{ done: false },
],
},
],
},
});
Read nested filters from the inside out: the $and block matches overdue open tasks, and the $or widens that to also include anything high priority.
Edge cases
A few behaviors are worth committing to memory, because they are easy to hit with dynamic filters built from user input:
- An empty
$inarray matches nothing. Filtering by a selection list that happens to be empty returns zero rows, not all rows. - An empty
$ninarray excludes nothing, so it behaves as if the condition were not there. - Malformed operators throw. A typo like
$eqqor an operator given the wrong value type surfaces as a thrownDataErrorrather than silently matching nothing, so you will catch mistakes early.
When a filter comes from UI state (checkboxes, multi-selects), guard the empty case yourself: skip the $in clause entirely when the selection is empty if "no selection" should mean "show everything".
Ordering
The order option is a string of column:direction pairs. Direction is asc or desc, and you can sort by several columns by separating them with commas:
useQuery("posts", { order: "published_at:desc" });
useQuery("products", { order: "category:asc,price:desc" });
Pagination
Page through results with limit and offset. limit defaults to 20 and is capped at 100, so a bigger page size is silently impossible; plan your UI around pages of at most 100 rows. The count field on every query result is the total number of matching rows regardless of limit, which is what you need to render page numbers:
const { data, count } = useQuery("orders", {
filter: { status: "paid" },
order: "created_at:desc",
limit: 25,
offset: page * 25,
});
const totalPages = Math.ceil(count / 25);
For "load more" style lists, skip the offset math and use useInfiniteQuery from the Data API, which appends pages for you.
Filters decide which rows a query asks for, but the table's visibility level decides which rows the caller is allowed to see at all. Those rules are applied on top of your filter, server-side; see Security rules.