Skip to main content

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.

OperatorMeaningExample
$eqEquals (same as a bare value){ status: { $eq: "paid" } }
$neNot equal{ status: { $ne: "draft" } }
$gtGreater than{ price: { $gt: 100 } }
$gteGreater than or equal{ price: { $gte: 100 } }
$ltLess than{ stock: { $lt: 5 } }
$lteLess than or equal{ stock: { $lte: 5 } }
$inValue is in the array{ status: { $in: ["paid", "shipped"] } }
$ninValue is not in the array{ status: { $nin: ["cancelled", "refunded"] } }
$likePattern match, case sensitive, % as wildcard{ sku: { $like: "TEE-%" } }
$ilikePattern match, case insensitive{ title: { $ilike: "%invoice%" } }
$isMatches null or a boolean exactly{ archived_at: { $is: null } }
$orAny of an array of filters matches{ $or: [{ done: true }, { priority: "high" }] }
$andAll 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 $in array matches nothing. Filtering by a selection list that happens to be empty returns zero rows, not all rows.
  • An empty $nin array excludes nothing, so it behaves as if the condition were not there.
  • Malformed operators throw. A typo like $eqq or an operator given the wrong value type surfaces as a thrown DataError rather than silently matching nothing, so you will catch mistakes early.
tip

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.