| name | setup |
| description | Install a TanStack Table v9 framework adapter and wire up a first table with `tableFeatures({...})` declaring `_features`, an `_rowModels` map of factory results (`createSortedRowModel(sortFns)`, `createFilteredRowModel(filterFns)`, `createPaginatedRowModel()`, …), a `createColumnHelper<typeof _features, TData>()` column set, and the framework `useTable` / `injectTable` / `createTable` / `constructTable` entry point. Covers the registry model, why `_features` must be module-scoped, when to reach for `stockFeatures`, and `coreFeatures`.
|
| type | core |
| library | tanstack-table |
| library_version | 9.0.0-alpha.48 |
| requires | ["state-management","column-definitions"] |
| sources | ["TanStack/table:docs/installation.md","TanStack/table:docs/overview.md","TanStack/table:docs/guide/tables.md","TanStack/table:docs/guide/features.md","TanStack/table:packages/table-core/src/index.ts","TanStack/table:examples/react/basic-use-table/src/main.tsx"] |
This skill builds on tanstack-table/state-management and tanstack-table/column-definitions. Read those first for the v9 atom model and the createColumnHelper<typeof _features, TData>() shape.
Setup
TanStack Table v9 separates a framework-agnostic core (@tanstack/table-core) from per-framework adapters. Every table — vanilla or framework — must declare two new v9-required options at construction time: _features (the registry of feature plugins) and _rowModels (the map of pipeline factories).
import {
constructTable,
tableFeatures,
createColumnHelper,
rowSortingFeature,
createSortedRowModel,
sortFns,
} from '@tanstack/table-core'
type Person = { firstName: string; lastName: string; age: number }
const _features = tableFeatures({ rowSortingFeature })
const columnHelper = createColumnHelper<typeof _features, Person>()
const columns = columnHelper.columns([
columnHelper.accessor('firstName', { header: 'First' }),
columnHelper.accessor('lastName', { header: 'Last' }),
columnHelper.accessor('age', { header: 'Age', sortFn: 'alphanumeric' }),
])
const data: Person[] = [
{ firstName: 'Ada', lastName: 'Lovelace', age: 36 },
{ firstName: 'Grace', lastName: 'Hopper', age: 85 },
]
const table = constructTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
},
columns,
data,
})
table.getHeaderGroups()
table.getRowModel().rows
Framework adapters wrap this with reactivity:
| Framework | Entry point | Package |
|---|
| React, Preact, Vue, Solid, Lit | useTable / createTable / TableController | @tanstack/<fw>-table |
| Angular | injectTable | @tanstack/angular-table |
| Svelte 5 | createTable (runes) | @tanstack/svelte-table |
| Vanilla JS | constructTable + storeReactivityBindings() | @tanstack/table-core |
Core Patterns
Minimum table (no extra features)
const _features = tableFeatures({})
const table = constructTable({
_features,
_rowModels: {},
columns,
data,
})
tableFeatures({}) is the canonical "I only want the core read pipeline" setup.
Add features and their row models together
import {
rowSortingFeature,
rowPaginationFeature,
columnFilteringFeature,
createSortedRowModel,
createFilteredRowModel,
createPaginatedRowModel,
sortFns,
filterFns,
} from '@tanstack/table-core'
const _features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
columnFilteringFeature,
})
const table = constructTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
filteredRowModel: createFilteredRowModel(filterFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
})
Each feature and its row-model factory are registered together — TypeScript exposes the feature's APIs (table.setSorting, table.nextPage, table.setColumnFilters) only when its feature plugin is present in _features.
Vanilla JS reactivity binding
import { constructTable, tableFeatures } from '@tanstack/table-core'
import { storeReactivityBindings } from '@tanstack/table-core'
const _features = tableFeatures({})
const table = constructTable({
_features,
_rowModels: {},
columns,
data,
_rowModelFns: {},
_processingMode: 'core',
})
const unsub = table.atoms.sorting.subscribe((sorting) => {
console.log('sorting changed', sorting)
})
stockFeatures for v8-like "everything on"
Reach for this only as a transitional aid. It re-introduces v8 bundle size (~15–20kb) and undoes v9's tree-shaking benefit.
import { stockFeatures, tableFeatures } from '@tanstack/table-core'
const _features = tableFeatures(stockFeatures)
Common Mistakes
[CRITICAL] API/state slice missing because the feature was not registered in _features
Wrong:
const _features = tableFeatures({})
const table = useTable({ _features, _rowModels: {}, columns, data })
table.setSorting([{ id: 'age', desc: true }])
Correct:
const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
})
In v9, _features is a tree-shakeable registry — TypeScript hides APIs for unregistered features and the runtime atom is never created. Agents who see table.setColumnFilters "missing" often incorrectly conclude the API was removed.
Source: maintainer interview (Phase 4, 2026-05-17)
[CRITICAL] Hallucinating v7 / v8 useReactTable + getCoreRowModel() shape
Wrong:
import { useTable, useSortBy } from 'react-table'
const table = useTable({ columns, data }, useSortBy)
import { useReactTable, getCoreRowModel } from '@tanstack/react-table'
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
})
Correct:
import {
useTable,
tableFeatures,
rowSortingFeature,
createSortedRowModel,
sortFns,
} from '@tanstack/react-table'
const _features = tableFeatures({ rowSortingFeature })
const table = useTable({
_features,
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns,
data,
})
v7→v8 and v8→v9 both reshaped the API substantially. Agents trained on older data confidently emit v7/v8 shapes; v9 enforces _features + _rowModels.
Source: maintainer interview (Phase 4, 2026-05-17)
[HIGH] Calling tableFeatures({}) inside the component body
Wrong:
function MyTable() {
const _features = tableFeatures({ rowSortingFeature })
const table = useTable({ _features, _rowModels: {}, columns, data })
}
Correct:
const _features = tableFeatures({ rowSortingFeature })
function MyTable() {
const table = useTable({ _features, _rowModels: {}, columns, data })
}
A fresh _features reference on each render churns the table's feature registry — the same way unstable columns or data cause infinite re-renders. Hoist to module scope or memoize.
Source: docs/guide/data.md; examples/react/basic-use-table/src/main.tsx
[HIGH] Adding a row-model factory without registering its feature
Wrong:
const _features = tableFeatures({ rowPaginationFeature })
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
})
Correct:
const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
})
Row-model factories only run if their matching feature is in _features. Runtime silently degrades.
Source: docs/guide/row-models.md; examples/react/basic-external-atoms/src/main.tsx
[CRITICAL] Reimplementing what built-in APIs already provide
Wrong:
const [sorting, setSorting] = useState([])
const sortedData = useMemo(() => [...data].sort((a, b) => ), [data, sorting])
Correct:
const table = useTable({
_features: tableFeatures({ rowSortingFeature }),
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns,
data,
})
TanStack Table IS a state-management coordinator. The maintainer flags hand-rolled state/sort/filter loops as the #1 tell that an AI wrote the code. Reach for table.setSorting, row.toggleSelected, table.nextPage, table.setColumnFilters, column.toggleVisibility first.
Source: maintainer interview (Phase 4, 2026-05-17)
[HIGH] Bundling stockFeatures when only a few features are used
Wrong:
import { stockFeatures, tableFeatures } from '@tanstack/react-table'
const _features = tableFeatures(stockFeatures)
Correct:
import {
tableFeatures,
rowSortingFeature,
rowPaginationFeature,
} from '@tanstack/react-table'
const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
Tree-shaking via _features is the headline reason for the v9 redesign. stockFeatures exists as a v8-style transitional escape hatch, not a default.
Source: maintainer interview (Phase 4, 2026-05-17)
[CRITICAL] Empty array literal for data causes infinite re-renders
Wrong:
const table = useTable({
_features,
_rowModels: {},
columns,
data: items ?? [],
})
Correct:
const EMPTY: Person[] = []
const table = useTable({
_features,
_rowModels: {},
columns,
data: items ?? EMPTY,
})
A new [] reference each render means the table sees a fresh data prop and rebuilds row models. Top recurring beginner issue.
Source: https://github.com/TanStack/table/issues/4566; https://github.com/TanStack/table/issues/6002
See also
tanstack-table/state-management — atom model, ownership precedence, state slices
tanstack-table/column-definitions — createColumnHelper<typeof _features, TData>() and getRowId
tanstack-table/migrate-v8-to-v9 — full rename + restructure table for v8 → v9 upgrades