Advanced filter
The column filters cover one column at a time, joined with AND. The advanced filter is for the questions that shape does not reach: OR across different columns, negation, nested groups, and comparisons against an aggregate of the rows themselves.
The engine ships in @svgrid/enterprise. The free grid carries the config type
and the seam to plug an engine in, so nothing in @svgrid/grid depends on the
commercial package.
Setup
<script>
import { SvGrid } from '@svgrid/grid'
import { SvAdvancedFilter, enableAdvancedFilter } from '@svgrid/enterprise'
enableAdvancedFilter()
let api = $state(null)
</script>
{#if api}
<SvAdvancedFilter {api} />
{/if}
<SvGrid {data} {columns} onApiReady={(a) => (api = a)} />
The panel is mounted by you, beside the grid, the same way SvGridAlerts is.
There is no grid-side renderer to register, so you decide whether it sits in a
sidebar, a drawer or a dialog.
The expression
An expression is a JSON AST, so a saved view is just data:
const expr = {
kind: 'and',
parts: [
{ kind: 'cmp', column: 'region', op: 'equals', value: 'EMEA' },
{ kind: 'cmp', column: 'churnRisk', op: 'in', value: ['medium', 'high'] },
],
}
api.setAdvancedFilter(expr)
cmp uses the same operator union as the filter row, and leaf comparisons
delegate to the grid's own applyExcelFilter. A "greater than" in the advanced
filter is byte-for-byte a "greater than" in the column menu, rather than a
second implementation that drifts.
| Node | Means |
|---|---|
cmp |
One column compared with a filter-row operator |
and / or |
Combine parts |
not |
Negate |
scalarCmp |
Compare two scalar expressions, so column maths and aggregates |
const |
{ kind: 'const', value: true } is how "no filter" is spelled |
Comparing a row to an aggregate
This is the one the column filters cannot express at all, because it compares each row to a value computed from the rows that survived the other filters:
api.setAdvancedFilter({
kind: 'scalarCmp',
left: { kind: 'col', id: 'arr' },
op: '>',
right: { kind: 'agg', fn: 'avg', column: 'arr' },
})
The aggregate is folded once per filter change, not once per row. A naive
evaluator re-scans the row set for every row, which makes SUM(x) > N
quadratic; at a few thousand rows that is the difference between instant and
visibly stuck.
The builder
The panel edits the expression as a tree: conditions, and groups holding more conditions. Add group nests one level, and a nested group defaults to the opposite combinator of its parent, since nesting an "all" inside an "all" means nothing and would only have to be corrected. NOT on a group negates it.
Nesting is capped at four levels. Beyond that the panel keeps the expression in text mode rather than drawing a tree too deep to read.
What the builder will not show
Column maths and aggregates have no condition-row representation, so an expression containing them stays in text mode and the Builder tab is disabled with a tooltip explaining why. That is deliberate: a tab that looks clickable and then refuses reads as a bug, when the refusal is a property of the expression.
not around a single condition is also left to text mode. The grid already
has negative operators (notEquals, notContains), and offering two spellings
of one thing in the builder makes the UI worse, not better.
Text mode always accepts everything:
region = "EMEA" AND (arr > 300000 OR seats > 150)
Applying, and what happens on failure
The panel holds a draft and only touches the grid on Apply. The editor emits on every keystroke, which is right for an alert rule but here would re-run the whole filter pipeline per character. Holding the draft also makes the live "matches N" counter a real preview of what Apply would do rather than a lagging echo of what already happened.
Filtering fails open. If no engine is registered, if the expression fails to compile, or if anything throws, the rows are left untouched:
A half-filtered grid is indistinguishable from a correctly filtered one. Silently dropping rows because of an internal error is the one outcome worth ruling out, so the failure is visible instead: the panel says an expression is set but no engine is running it.
That is also why @svgrid/grid alone shows every row when you set an expression
without enableAdvancedFilter() - the free package never pretends to filter.
The toolbar indicator
When an advanced filter is set, the grid shows a chip above the table naming it,
with a control to clear it. The filter is authored in a panel you placed, which
may be scrolled away or behind a drawer; without the chip, rows are missing with
nothing on screen to say why. Both strings go through localeText
(advancedFilterActive, advancedFilterClear).
Server-side
ServerFilterModel.expression carries the expression to your backend. The
contract is all-or-nothing: apply the whole expression and set
appliedExpression: true, or apply none of it.
If a backend ignores it, the grid does not filter the page it already has.
Doing so would turn "3 of 1,000,000 match" into a confident lie, and paging
would be incoherent because the next page would re-filter a different slice.
Instead the state carries expressionUnapplied, the grid warns once, and the
rows are left alone so you can tell the user the filter did not run.
API
| Method | Does |
|---|---|
api.setAdvancedFilter(expr) |
Apply an expression |
api.getAdvancedFilter() |
The current expression, or null |
api.clearAdvancedFilter() |
Remove it |
api.isAdvancedFilterActive() |
True only when an engine is actually running it |
api.clearAllFilters() clears the advanced filter too, since it promises every
filter surface. Saved views round-trip it through an optional advancedFilter
key, so views saved before this feature existed load unchanged.
See also
- Filtering overview
- Filter conditions - the per-column shape
- Filter API