Skip to main content
Filters scope a search to rows that match conditions on your metadata fields (plus id and text). A filter is either a single condition or a logical combination of conditions, and the whole structure is typed against your metadata.
const results = await table.search("machine learning", {
  filter: { field: "category", op: "=", value: "AI" },
});

Conditions

A single condition names a field, an operator op, and (for most operators) a value. The field is one of your metadata keys, or id / text — names resolve to the underlying columns automatically.
{ field: "category", op: "=", value: "AI" }

Operators

opvalueMeaning
=string | numberEquals.
> >= < <=string | numberComparison.
IN(string | number)[]Value is in the list.
LIKEstringSQL LIKE pattern (% wildcard).
NOT LIKEstringNegated LIKE pattern.
IS NULL / IS NOT NULLField is (not) null.
IS TRUE / IS NOT TRUEBoolean truthiness.
IS FALSE / IS NOT FALSEBoolean falsiness.
The IS … operators take no value.
// Comparison
{ field: "score", op: ">=", value: 0.8 }

// Membership
{ field: "category", op: "IN", value: ["AI", "Database"] }

// Pattern
{ field: "title", op: "LIKE", value: "Intro%" }

// Null and boolean checks
{ field: "summary", op: "IS NOT NULL" }
{ field: "published", op: "IS TRUE" }

Combining conditions

Wrap conditions in AND, OR, or NOT to build compound filters. These nest freely, so you can express any boolean logic.

AND / OR

{
  AND: [
    { field: "category", op: "=", value: "AI" },
    { field: "score", op: ">=", value: 0.7 },
  ],
}
{
  OR: [
    { field: "category", op: "=", value: "AI" },
    { field: "category", op: "=", value: "Database" },
  ],
}

NOT

NOT negates a single nested filter (which may itself be a condition or a group).
{ NOT: { field: "title", op: "LIKE", value: "%draft%" } }

Nesting

const results = await table.search("machine learning", {
  filter: {
    AND: [
      { field: "category", op: "=", value: "AI" },
      {
        OR: [
          { field: "score", op: ">=", value: 0.9 },
          { field: "title", op: "LIKE", value: "Intro%" },
        ],
      },
      { NOT: { field: "title", op: "LIKE", value: "%draft%" } },
    ],
  },
});

Types

type FilterField<T> = keyof T["metadata"] | "id" | "text";

type FilterCondition<T> =
  | { field: FilterField<T>; op: ">" | ">=" | "<" | "<=" | "="; value: string | number }
  | { field: FilterField<T>; op: "IN"; value: (string | number)[] }
  | { field: FilterField<T>; op: "LIKE" | "NOT LIKE"; value: string }
  | { field: FilterField<T>; op: "IS NULL" | "IS NOT NULL" }
  | { field: FilterField<T>; op: "IS TRUE" | "IS NOT TRUE" | "IS FALSE" | "IS NOT FALSE" };

type Filter<T> =
  | FilterCondition<T>
  | { AND: Filter<T>[] }
  | { OR: Filter<T>[] }
  | { NOT: Filter<T> };
Because filters are typed against T["metadata"], an unknown field name or a value of the wrong type is a compile-time error.
Filters compile to SQL predicates over the metadata.<field> columns. For fields you filter on frequently, add a scalar index with createIndex() so the engine can skip non-matching rows instead of scanning them.