Column filtering
FilterState, the four entry kinds, and defaultFilter — the state and logic layer, ahead of any built-in filter UI.
Last updated August 7, 2026
This page describes the filtering engine — the state shape and the functions that apply it. There is no header or toolbar filter UI yet; that's coming in a future release, built on exactly what's described here. Today, filtering is available two ways: seed a grid with defaultFilter, or filter dataSource yourself before it reaches the grid, using the same exported functions a future built-in UI will use internally.
The four ways to match#
A FilterState<Row> is an array of FilterEntry<Row>, ANDed together — a row has to satisfy every entry to stay. Each entry is one of four kinds:
TextFilterEntry—{ columnId?, query }. SQL LIKE-style with%: a bare query is an exact match,%text%is contains,text%is starts-with,%textis ends-with. Always case-insensitive. Every value is stringified before matching.ValueFilterEntry—{ columnId?, value }. Strict equality against a column's actualnumber,boolean, orDatevalue — only when the column's owntypeagrees. Anumbervalue never matches astringcolumn, even one that happens to display the same digits.PredicateFilterEntry—{ columnId?, predicate }. Custom matching logic for anything the other two can't express, such as a numeric range. Called once per row with the resolved column value (orundefined, for a global entry) and the whole row. Negation lives here too —predicate: (value) => value !== "Archived"— there's no separate "not" entry kind.GroupFilterEntry—{ combinator: "and" | "or", entries }. Composes other entries — including further groups, to any depth — with an explicit combinator instead of the implicit AND every top-levelFilterStateuses. This is how OR enters the picture: the top level stays AND-only, and a group is where you opt into OR, exactly where you need it.
Any entry except a group may omit columnId to match against every column instead of one — a free-text search, for example.
Seed a grid already filtered#
<DataGridComponent
columns={columns}
dataSource={rows}
defaultFilter={[{ columnId: "status", query: "active" }]}
/>;Stacking entries ANDs them; nesting a group is how to OR — the canonical case being "match one of several values for the same field":
const statusIsActiveOrPending: FilterEntry<Row> = {
combinator: "or",
entries: [
{ columnId: "status", value: "active" },
{ columnId: "status", value: "pending" },
],
};All four kinds combine freely in one FilterState — plain entries ANDed at
the top level, a group opting into OR (or a nested AND) for just the part
that needs it, and a predicate wherever query/value can't express the
check:
const filter: FilterState<Row> = [
{ columnId: "name", query: "%smith%" },
{
combinator: "or",
entries: [
{ columnId: "status", value: "active" },
{ columnId: "status", value: "pending" },
],
},
{
columnId: "age",
predicate: (value) => typeof value === "number" && value >= 18 && value <= 65,
},
];
<DataGridComponent columns={columns} dataSource={rows} defaultFilter={filter} />;Keeps rows whose name contains "smith" and whose status is active or pending and whose age is between 18 and 65 — the OR stays scoped to the status check, while the name and age entries AND against it and each other at the top level.
Filter data before it reaches the grid#
Without built-in filter UI yet, a custom search box built today applies its own filter directly, ahead of dataSource:
import { filterRows } from "@gridkitjs/core";
const filtered = filterRows(rows, [{ query: searchText }], resolvedColumns);
<DataGridComponent columns={columns} dataSource={filtered.map((r) => r.row)} />;matchesQuery(value, query) is available for a single ad hoc check — the same matcher TextFilterEntry uses internally, exposed standalone.
Props#
| Prop | Type | Default | Description |
|---|---|---|---|
defaultFilter | FilterState<Row> | The filter to start with — every applied entry, ANDed together. Uncontrolled. |