| name | migrate-v8-to-v9 |
| description | Mechanical breaking-change migration from TanStack Table v8 to v9 at the `@tanstack/table-core` level. Covers hook/entry rename (`useReactTable`/`createSolidTable`/… → `useTable`/`injectTable`/`createTable`/ `constructTable`), the new required `_features` + `_rowModels` options, `createColumnHelper<TData>()` → `createColumnHelper<typeof _features, TData>()`, row-model factory rename (`getCoreRowModel()` → automatic; `getSortedRowModel()` → `createSortedRowModel(sortFns)`; same for filtered/paginated/grouped/expanded/ faceted), `table.getState()` → `table.store.state` / `table.atoms.<slice>.get()`, sorting renames (`sortingFn` → `sortFn`, etc.), `enablePinning` split, column sizing/resizing split, underscore-prefixed APIs becoming public, `RowData` type tightening, TFeatures-first generics, the `useLegacyTable` React escape hatch (deprecated, removed in v10), and the `stockFeatures` v8-style "everything on" registry.
|
| type | lifecycle |
| library | tanstack-table |
| library_version | 9.0.0-alpha.48 |
| requires | ["setup","state-management","column-definitions"] |
| sources | ["TanStack/table:docs/framework/table-core/guide/migrating.md","TanStack/table:docs/framework/react/guide/use-legacy-table.md","TanStack/table:packages/react-table/src/legacy.ts"] |
Setup
v9 is a substantial reshape, not a tweak. The breaking changes group into:
- Hook/entry rename per adapter.
_features + _rowModels are required — features are tree-shaken.
- Column helper generic order —
<TFeatures, TData> not <TData>.
- Row-model factories moved out of root options, into
_rowModels, and now take their *Fns argument.
- State surface renamed —
table.getState() → table.store.state / table.atoms.<slice>.get() / table.state (selector).
- Sorting names:
sortingFn → sortFn, sortingFns → sortFns, getSortingFn() → getSortFn(), type SortingFn → SortFn.
enablePinning split into enableColumnPinning + enableRowPinning (table-level); per-column enablePinning stays.
- Column resizing split out of column sizing.
columnSizingInfo state → columnResizing. onColumnSizingInfoChange → onColumnResizingChange.
- Underscore-prefixed APIs are public now — drop the
_ prefix (row._getAllCellsByColumnId() → row.getAllCellsByColumnId(), table._getFacetedRowModel() → public, etc.).
- Generics now lead with
TFeatures — Column<TFeatures, TData, TValue>, Row<TFeatures, TData>, ColumnMeta<TFeatures, TData, TValue>.
RowData tightened from unknown | object | any[] to Record<string, any> | Array<any>.
data and columns are readonly in v9 — flow changes through state, don't mutate.
For React projects that cannot migrate every table at once, useLegacyTable from @tanstack/react-table/legacy accepts the v8 shape on top of the v9 engine. Deprecated, ships every feature, no table.Subscribe. Removed in v10.
Core Patterns
Full v9 equivalent for the most common v8 shape
import {
useTable,
tableFeatures,
rowSortingFeature,
rowPaginationFeature,
columnFilteringFeature,
columnSizingFeature,
columnResizingFeature,
createColumnHelper,
createSortedRowModel,
createFilteredRowModel,
createPaginatedRowModel,
sortFns,
filterFns,
} from '@tanstack/react-table'
import type { ColumnDef } from '@tanstack/react-table'
const _features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
columnFilteringFeature,
columnSizingFeature,
columnResizingFeature,
})
const columnHelper = createColumnHelper<typeof _features, Person>()
const columns: ColumnDef<typeof _features, Person>[] = columnHelper.columns([
columnHelper.accessor('name', {
header: 'Name',
sortFn: 'alphanumeric',
}),
])
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
filteredRowModel: createFilteredRowModel(filterFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
enableColumnPinning: true,
enableRowPinning: true,
})
const allState = table.store.state
const sorting = table.atoms.sorting.get()
const cells = row.getAllCellsByColumnId()
v8 muscle-memory anti-shape
import {
useReactTable,
getCoreRowModel,
getFilteredRowModel,
getSortedRowModel,
getPaginationRowModel,
createColumnHelper,
sortingFns,
filterFns,
flexRender,
} from '@tanstack/react-table'
import type { ColumnDef, Row } from '@tanstack/react-table'
const columnHelper = createColumnHelper<Person>()
const columns: ColumnDef<Person>[] = [
{ accessorKey: 'name', header: 'Name', sortingFn: 'alphanumeric' },
]
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
filterFns,
sortingFns,
enablePinning: true,
onColumnSizingInfoChange: setInfo,
})
const all = table.getState()
const cells = row._getAllCellsByColumnId()
Transitional useLegacyTable (React only)
import {
useLegacyTable,
getCoreRowModel,
legacyCreateColumnHelper,
} from '@tanstack/react-table/legacy'
const legacyHelper = legacyCreateColumnHelper<Person>()
const legacyTable = useLegacyTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
})
This accepts the v8 shape on top of the v9 engine. Deprecated, ships every feature, no table.Subscribe, no atoms. Removed in v10. Use to unblock incremental migration only — not as a long-term API. Angular has no useLegacyTable equivalent; Angular projects must migrate directly.
Common Mistakes
[CRITICAL] Hallucinating react-table v7 / pre-v9 API names
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,
})
Every major release of TanStack Table has been a substantial upgrade. Agents trained on v7 or v8 will confidently emit shapes that no longer exist. This is the #2 AI failure (after reimplementing built-ins).
Source: maintainer interview (Phase 4, 2026-05-17)
[CRITICAL] Importing pre-bundled getCoreRowModel / getSortedRowModel etc.
Wrong:
const table = useTable({
_features,
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})
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,
})
In v9, row models live under _rowModels and the factories REQUIRE their *Fns argument for tree-shaking: createFilteredRowModel(filterFns), createSortedRowModel(sortFns), createGroupedRowModel(aggregationFns). Core is automatic.
Source: PR #6234 (atoms refactor); packages/table-core/src/index.ts
[CRITICAL] createColumnHelper<TData>() (v8 arity)
Wrong:
const columnHelper = createColumnHelper<Person>()
Correct:
const _features = tableFeatures({ rowSortingFeature })
const columnHelper = createColumnHelper<typeof _features, Person>()
v9 requires <TFeatures, TData>. typeof _features is the standard idiom — declare features once at module scope and reuse the type.
Source: packages/table-core/src/helpers/columnHelper.ts; docs/framework/react/guide/migrating.md
[HIGH] Reading state via table.getState()
Wrong:
const all = table.getState()
Correct:
const all = table.store.state
const sorting = table.atoms.sorting.get()
const selected = table.state
table.getState() was removed. There are three reads now, picked by what you need.
Source: docs/framework/table-core/guide/migrating.md
[HIGH] Sorting renames missed
Wrong:
{ accessorKey: 'age', sortingFn: 'alphanumeric' }
useTable({ sortingFns: { ... } })
column.getSortingFn()
Correct:
columnHelper.accessor('age', { sortFn: 'alphanumeric' })
createSortedRowModel(sortFns)
column.getSortFn()
v9 renamed every sorting API: sortingFn → sortFn, sortingFns → sortFns, type SortingFn → SortFn, getSortingFn() → getSortFn(). TypeScript surfaces these but agents try v8 names first.
Source: packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts
[HIGH] Using enablePinning at the table level
Wrong:
const table = useTable({
_features: tableFeatures({ columnPinningFeature, rowPinningFeature }),
enablePinning: true,
})
Correct:
const table = useTable({
_features: tableFeatures({ columnPinningFeature, rowPinningFeature }),
enableColumnPinning: true,
enableRowPinning: true,
})
columnHelper.accessor('id', { enablePinning: false })
v9 split enablePinning (table-level) into enableColumnPinning + enableRowPinning. The bare name now refers ONLY to per-column opt-out.
Source: packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts
[HIGH] Treating column resizing as part of column sizing
Wrong:
const table = useTable({
_features: tableFeatures({ columnSizingFeature }),
onColumnSizingInfoChange: setInfo,
})
Correct:
const table = useTable({
_features: tableFeatures({
columnSizingFeature,
columnResizingFeature,
}),
onColumnResizingChange: setResizing,
})
v9 split them: columnSizingFeature for fixed widths, columnResizingFeature for drag-to-resize. State key columnSizingInfo → columnResizing; option onColumnSizingInfoChange → onColumnResizingChange.
Source: docs/framework/table-core/guide/migrating.md
[MEDIUM] Calling underscore-prefixed APIs
Wrong:
row._getAllCellsByColumnId()
table._getFacetedRowModel()
table._getFacetedMinMaxValues()
table._getFacetedUniqueValues()
table._getPinnedRows()
Correct:
row.getAllCellsByColumnId()
table.getFacetedRowModel()
table.getFacetedMinMaxValues()
table.getFacetedUniqueValues()
table.getPinnedRows()
All became public — drop the underscore.
Source: docs/framework/table-core/guide/migrating.md
[MEDIUM] Module augmentation with v8 generic arity
Wrong:
declare module '@tanstack/react-table' {
interface ColumnMeta<TData, TValue> {
customProp: string
}
}
Correct:
declare module '@tanstack/react-table' {
interface ColumnMeta<TFeatures, TData, TValue> {
customProp: string
}
}
v9 added TFeatures as the first generic. Module augmentation silently widens types if arity is wrong.
Source: docs/framework/table-core/guide/migrating.md
[MEDIUM] Mutating data or columns in place
Wrong:
const data: Person[] = []
function addRow(row: Person) {
data.push(row)
rerender()
}
Correct:
const [data, setData] = useState<Person[]>([])
function addRow(row: Person) {
setData((prev) => [...prev, row])
}
PR #6183 makes data and columns readonly to force changes through state.
Source: PR #6183
[MEDIUM] Reaching for useLegacyTable in new code
Wrong:
import { useLegacyTable, getCoreRowModel } from '@tanstack/react-table/legacy'
const table = useLegacyTable({
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,
})
useLegacyTable is React-only, deprecated, bundles every feature, doesn't support table.Subscribe, and is removed in v10. It exists to unblock incremental migration — not as a long-term API.
Source: packages/react-table/src/legacy.ts; docs/framework/react/guide/use-legacy-table.md
See also
tanstack-table/setup — what the v9-native shape looks like
tanstack-table/state-management — table.store.state / table.atoms / table.state ownership
tanstack-table/column-definitions — createColumnHelper<typeof _features, TData>() generic order