Capítulo 20 de 54
meta is a free-form, typed pass-through slot — TanStack Table never reads or writes it — for carrying your own context (an updateData callback, a filterVariant tag) anywhere the table/column instance is available. v9 adds a per-table typed way to declare its shape (metaHelper + tableMeta/columnMeta/filterMeta slots on tableFeatures()), replacing v8's global declaration merging.
meta table option → read anywhere via table.options.meta (e.g. an updateData(rowIndex, columnId, value) callback passed down to editable cells); meta on a column definition → read via column.columnDef.meta (e.g. filterVariant: 'range' telling a header which filter UI to render); filterMeta → metadata a custom filter function attaches per-row via addMeta(...) during filtering (e.g. a fuzzy-match ranking score), read back via row.columnFiltersMeta[columnId] in a paired sort function or cell renderer.tableFeatures({ ..., tableMeta: metaHelper<MyTableMeta>(), columnMeta: metaHelper<MyColumnMeta>() }). From then on, typeof features carries the meta types everywhere (useTable, createColumnHelper, ColumnDef, Column) with zero extra generics.metaHelper<T>() just returns {} cast to T — you could write {} as MyTableMeta yourself, but metaHelper reads as type-only at a glance and avoids a @typescript-eslint/no-unnecessary-type-assertion false positive (whose autofix would silently delete the cast) when your meta type is all-optional properties.meta table option / column-def meta property exactly as before.features objects can declare completely different meta shapes — unlike v8, there's no single global TableMeta/ColumnMeta interface every table shares.declare module '@tanstack/react-table' { interface TableMeta<TFeatures, TData> {...} }) — the only v9 change is that TFeatures is now the first generic on both interfaces. Precedence: a features object with its own tableMeta/columnMeta slot replaces (not merges with) the global declaration-merged interface for tables built from it; tables whose features declare no slot fall back to the global interfaces.table.toggleDensity()), write a custom feature instead (see Custom Features Guide); it plugs into the same features option with the same type inference.interface MyTableMeta { updateData: (rowIndex: number, columnId: string, value: unknown) => void }
interface MyColumnMeta { filterVariant?: 'text' | 'range' | 'select' }
const features = tableFeatures({
rowSortingFeature,
tableMeta: metaHelper<MyTableMeta>(),
columnMeta: metaHelper<MyColumnMeta>(),
})
const table = useTable({
features, columns, data,
meta: { updateData: (rowIndex, columnId, value) => { /* ... */ } },
})
// anywhere the table/column is available, fully typed:
table.options.meta?.updateData(0, 'age', 42)
column.columnDef.meta?.filterVariant // 'text' | 'range' | 'select' | undefined
tableFeatures gives full type inference everywhere downstream, with the actual values still passed through the ordinary meta options.filterMeta for a fuzzy-filter ranking score — a custom filter function computes a rank via addMeta, and a paired sort function reads it back through row.columnFiltersMeta[columnId] to sort by relevance instead of raw value:
interface FuzzyFilterMeta { itemRank?: RankingInfo }
type FuzzyFeatures = TableFeatures & { filterMeta: FuzzyFilterMeta }
const fuzzyFilter: FilterFn<FuzzyFeatures, RowData> = (row, columnId, value, addMeta) => {
const itemRank = rankItem(row.getValue(columnId), value)
addMeta({ itemRank }) // stash the rank for this row/column
return itemRank.passed
}
const fuzzySort: SortFn<FuzzyFeatures, Person> = (rowA, rowB, columnId) => {
const rank = rowA.columnFiltersMeta[columnId]
return rank ? compareItems(rank.itemRank!, rowB.columnFiltersMeta[columnId]?.itemRank!) : 0
}
const features = tableFeatures({
columnFilteringFeature, rowSortingFeature,
filterFns: { fuzzy: fuzzyFilter },
sortFns: { fuzzy: fuzzySort },
filterMeta: metaHelper<FuzzyFilterMeta>(),
})
This is the mechanism behind "sort by search relevance after a fuzzy filter" — a pattern that has no other clean way to pass per-row filter by-products into a sort comparator.
metaHelper/tableMeta/columnMeta slots by default in v9 — global declaration merging is legacy-compatible, not the recommended path.filterMeta is specifically for custom filter functions that need to hand a by-product (a ranking score, a match span) to a sort function or renderer.meta isn't expressive enough.meta is set alongside accessorKey/cell/etc.