| name | filtering |
| description | Filter rows in TanStack Table v9 with the `filteredRowModel` pipeline stage. Covers `columnFilteringFeature` + `globalFilteringFeature` + `columnFacetingFeature`, `createFilteredRowModel(filterFns)`, `createFacetedRowModel()` / `createFacetedUniqueValues()` / `createFacetedMinMaxValues()`, fuzzy filtering with `@tanstack/match-sorter-utils`, the built-in `filterFns` registry, custom `filterFn` + module augmentation, `state.columnFilters` (Array<{ id, value }>), `state.globalFilter`, `column.setFilterValue` / `setColumnFilters` / `setGlobalFilter`, `column.getFacetedUniqueValues` / `column.getFacetedMinMaxValues`, `manualFiltering`, `filterFromLeafRows`, `maxLeafRowFilterDepth`, `getColumnCanGlobalFilter`. Five subsystems: column-filtering, global-filtering, column-faceting, global-faceting, fuzzy-filtering.
|
| type | core |
| library | tanstack-table |
| library_version | 9.0.0-alpha.48 |
| requires | ["state-management","customizing-feature-behavior"] |
| sources | ["TanStack/table:docs/guide/column-filtering.md","TanStack/table:docs/guide/global-filtering.md","TanStack/table:docs/guide/column-faceting.md","TanStack/table:docs/guide/global-faceting.md","TanStack/table:docs/guide/fuzzy-filtering.md","TanStack/table:examples/react/filters/src/main.tsx","TanStack/table:examples/react/filters-faceted/src/main.tsx","TanStack/table:examples/react/filters-fuzzy/src/main.tsx"] |
This skill builds on tanstack-table/state-management and tanstack-table/customizing-feature-behavior. Read those first for the atom model and filterFn/globalFilterFn overrides.
Setup
Filtering has five subsystems in v9 — register only the features you need:
| Subsystem | Feature | Row-model |
|---|
| column-filtering | columnFilteringFeature | createFilteredRowModel(filterFns) |
| global-filtering | globalFilteringFeature | (same filteredRowModel) |
| column-faceting | columnFacetingFeature | createFacetedRowModel() + helpers |
| global-faceting | globalFacetingFeature | global versions of the helpers |
| fuzzy-filtering | (custom filterFn) | @tanstack/match-sorter-utils |
import {
tableFeatures,
columnFilteringFeature,
globalFilteringFeature,
rowPaginationFeature,
createFilteredRowModel,
createPaginatedRowModel,
filterFns,
} from '@tanstack/table-core'
import type { ColumnFiltersState } from '@tanstack/table-core'
const _features = tableFeatures({
columnFilteringFeature,
globalFilteringFeature,
rowPaginationFeature,
})
const table = constructTable({
_features,
_rowModels: {
filteredRowModel: createFilteredRowModel(filterFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
initialState: {
columnFilters: [] satisfies ColumnFiltersState,
globalFilter: '',
},
globalFilterFn: 'includesString',
})
table.setColumnFilters([{ id: 'firstName', value: 'Ada' }])
table.setGlobalFilter('Lovelace')
Core Patterns
Text / range / select column filters
function Filter({ column }) {
const firstValue = column
.getFacetedRowModel()
.flatRows[0]?.getValue(column.id)
const columnFilterValue = column.getFilterValue()
return typeof firstValue === 'number' ? (
<div>
<input
type="number"
value={(columnFilterValue as [number, number])?.[0] ?? ''}
onChange={(e) =>
column.setFilterValue((old: [number, number]) => [
e.target.value,
old?.[1],
])
}
placeholder="Min"
/>
<input
type="number"
value={(columnFilterValue as [number, number])?.[1] ?? ''}
onChange={(e) =>
column.setFilterValue((old: [number, number]) => [
old?.[0],
e.target.value,
])
}
placeholder="Max"
/>
</div>
) : (
<input
value={(columnFilterValue ?? '') as string}
onChange={(e) => column.setFilterValue(e.target.value)}
placeholder="Search…"
/>
)
}
Faceted filter UIs and fuzzy global search
Faceting requires createFacetedRowModel() as the base plus createFacetedUniqueValues() / createFacetedMinMaxValues(); fuzzy filtering wires a custom FilterFn backed by @tanstack/match-sorter-utils (rankItem / compareItems) with module augmentation for FilterFns + FilterMeta. Full examples in faceting-and-fuzzy.md.
Server-side filtering
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const { data } = useQuery({
queryKey: ['rows', columnFilters],
queryFn: () =>
fetch('/api/rows?' + serialize(columnFilters)).then((r) => r.json()),
})
const table = useTable({
_features: tableFeatures({ columnFilteringFeature }),
_rowModels: {},
data,
columns,
manualFiltering: true,
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
})
Filter tree data and keep matching descendants visible
const table = constructTable({
_features: tableFeatures({ columnFilteringFeature, rowExpandingFeature }),
_rowModels: {
filteredRowModel: createFilteredRowModel(filterFns),
expandedRowModel: createExpandedRowModel(),
},
columns,
data,
getSubRows: (r) => r.subRows,
filterFromLeafRows: true,
})
Common Mistakes
[HIGH] Forgetting createFacetedRowModel() while registering createFacetedUniqueValues() / createFacetedMinMaxValues()
Wrong:
const table = useTable({
_features: tableFeatures({ columnFacetingFeature, columnFilteringFeature }),
_rowModels: {
filteredRowModel: createFilteredRowModel(filterFns),
facetedUniqueValues: createFacetedUniqueValues(),
facetedMinMaxValues: createFacetedMinMaxValues(),
},
columns,
data,
})
Correct:
const table = useTable({
_features: tableFeatures({
columnFacetingFeature,
columnFilteringFeature,
rowPaginationFeature,
}),
_rowModels: {
filteredRowModel: createFilteredRowModel(filterFns),
paginatedRowModel: createPaginatedRowModel(),
facetedRowModel: createFacetedRowModel(),
facetedMinMaxValues: createFacetedMinMaxValues(),
facetedUniqueValues: createFacetedUniqueValues(),
},
columns,
data,
})
Without the base facetedRowModel, the unique/minMax helpers fall back to getPreFilteredRowModel() — facet values stop excluding the column's own active filter, and a select dropdown collapses to only the currently selected value once the user picks one.
Source: packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts; examples/react/filters-faceted/src/main.tsx
[HIGH] Setting manualFiltering: true without refetching data
Wrong:
const table = useTable({
_features: tableFeatures({ columnFilteringFeature }),
_rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
data,
columns,
manualFiltering: true,
})
Correct:
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const { data } = useQuery({
queryKey: ['rows', columnFilters],
queryFn: () =>
fetch('/api/rows?' + serialize(columnFilters)).then((r) => r.json()),
})
const table = useTable({
_features: tableFeatures({ columnFilteringFeature }),
_rowModels: {},
data,
columns,
manualFiltering: true,
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
})
With manualFiltering: true, table_getFilteredRowModel short-circuits and returns the core rows. Rows are NOT filtered client-side — you must refetch.
Source: docs/guide/column-filtering.md; packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts
[HIGH] Custom fuzzy filter without merging into filterFns
Wrong:
filteredRowModel: createFilteredRowModel({
fuzzy: fuzzyFilter,
}),
Correct:
import { filterFns } from '@tanstack/react-table'
filteredRowModel: createFilteredRowModel({
...filterFns,
fuzzy: fuzzyFilter,
}),
createFilteredRowModel replaces the registry with whatever you pass. Any column using a built-in name like 'includesString' becomes a no-op.
Source: examples/react/filters-fuzzy/src/main.tsx
[MEDIUM] Global filter silently skips columns with non-string/non-number values
Wrong:
const columns = [
columnHelper.accessor('createdAt', { header: 'Created' }),
columnHelper.accessor('name', { header: 'Name' }),
]
Correct:
const table = useTable({
_features: tableFeatures({ globalFilteringFeature }),
_rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
columns,
data,
globalFilterFn: 'includesString',
getColumnCanGlobalFilter: (column) => true,
})
columnHelper.accessor('createdAt', {
header: 'Created',
enableGlobalFilter: true,
})
globalFilteringFeature defaults getColumnCanGlobalFilter to a function that returns typeof value === 'string' || typeof value === 'number' sampled from the first row. Objects, dates, booleans, undefined all silently fail.
Source: packages/table-core/src/features/global-filtering/globalFilteringFeature.ts
[CRITICAL] Reimplementing what built-in APIs provide
Wrong:
const filteredData = useMemo(
() => data.filter(),
[data, query],
)
Correct:
const table = useTable({
_features: tableFeatures({ columnFilteringFeature }),
_rowModels: { filteredRowModel: createFilteredRowModel(filterFns) },
columns,
data,
})
table.setColumnFilters([{ id: 'name', value: 'Ada' }])
table.setColumnFilters, column.setFilterValue, table.setGlobalFilter honor reset behavior and internal invariants.
Source: maintainer interview (Phase 4, 2026-05-17)
See also
tanstack-table/customizing-feature-behavior — filterFn / globalFilterFn authoring, addMeta chain
tanstack-table/sorting — fuzzy sort pairing for match-sorter-utils
tanstack-table/row-expanding — filterFromLeafRows interaction with tree data
References
- faceting-and-fuzzy.md — faceted filter UIs (autocomplete + range slider) with
createFacetedRowModel/createFacetedUniqueValues/createFacetedMinMaxValues, fuzzy global search via @tanstack/match-sorter-utils, plus MEDIUM-priority failure modes: state + initialState collision, filterFromLeafRows semantics, 'auto' filter misdetection on null first row