Perform a complete Preact TanStack Table v8-to-v9 migration: remove React-through-preact/compat usage, adopt the native adapter, explicit features and row-model slots, Preact state and atoms, rendering, composition, types, and every shared API rename and semantic change. Use for migration plans, implementation, or audits.
Read @tanstack/table-core#migrate-v8-to-v9, getting-started, and table-state. Use this as the exhaustive Preact migration checklist. Verify the exact v9 APIs in the installed declarations before assuming a different v9 version has the same surface.
Framework prerequisite: Preact 10 or newer (preact >=10).
Target architecture
import {
createSortedRowModel,
rowSortingFeature,
tableFeatures,
useTable,
} from '@tanstack/preact-table'
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
})
const table = useTable({ features, columns, data })
V8 did not have a first-party Preact adapter; many Preact apps used @tanstack/react-table through preact/compat. V9 uses native @tanstack/preact-table. Remove compatibility aliases that existed only for Table after replacing the imports. Prefer explicit features as the end state; stockFeatures is only a kitchen-sink migration shortcut.
Complete breaking-change map
Adapter, construction, and features
v8
v9
@tanstack/react-table through preact/compat
Native @tanstack/preact-table
useReactTable(options)
useTable({ ...options, features })
Every feature bundled
Register used *Feature objects with tableFeatures()
sortFns, filterFns, aggregationFns slots in tableFeatures()
Removed rowModels: { ... } object
Direct named slots in tableFeatures()
In the registry slots, register individually imported built-ins (filterFn_includesString, sortFn_alphanumeric, aggregationFn_sum, and so on) under their conventional keys alongside custom functions; the full filterFns/sortFns/aggregationFns registry objects still work but bundle every built-in.
Register each prerequisite feature before its row-model slot. Stock features are cellSelectionFeature, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, rowExpandingFeature, rowPinningFeature, columnPinningFeature, columnVisibilityFeature, columnOrderingFeature, columnSizingFeature, columnResizingFeature, rowAggregationFeature, columnGroupingFeature, and columnFacetingFeature. Aggregation is independent from grouping: register rowAggregationFeature for aggregation APIs and add columnGroupingFeature only for grouped rows.
Preact state and subscriptions
v8
v9
table.getState()
Reactive table.state, snapshot table.store.state, or table.atoms.<slice>.get()
Top-level onStateChange
Per-slice callbacks or table.store.subscribe()
Whole-component state reads
Custom second-argument selector, table.Subscribe, or atom source
The default useTable selector subscribes the component to all registered state. Pass a selector for table.state, or () => null and subscribe lower:
Controlled state plus per-slice on[State]Change remains supported. For app-owned atoms, use useCreateAtom/useSelector from @tanstack/preact-store and pass them through options.atoms. An external atom wins over state for the same slice; do not mirror both ownership models.
Rendering and composition
Replace React-adapter flexRender(def, context) with <table.FlexRender cell={cell} /> or standalone <FlexRender ... />; the function remains for advanced cases.
Use tableOptions() for typed reusable option fragments.
Use createTableHook({ features, ...defaults }) for repeated app table conventions; it returns native app helpers such as useAppTable and createAppColumnHelper.
Call row/cell/column/header methods on their instance. Prototype methods lose this when destructured and do not appear in object spread, Object.keys, or JSON. Table-instance methods are not affected.
TypeScript and helpers
v8
v9
createColumnHelper<Person>()
createColumnHelper<typeof features, Person>()
Plain arrays that widen nested values
columnHelper.columns([...])
ColumnDef<TData>
ColumnDef<TFeatures, TData, TValue>
Column<TData>, Row<TData>, Table<TData>
Add TFeatures first
Cell<TData, TValue>
Cell<TFeatures, TData, TValue>
React module augmentation
Target @tanstack/preact-table
TableMeta<TData> / ColumnMeta<TData, TValue>
Add TFeatures, or use per-table tableMeta / columnMeta slots with metaHelper()
Global FilterFns, SortFns, AggregationFns, and FilterMeta augmentation
filterFns, sortFns, aggregationFns, and filterMeta feature slots
RowData = unknown
Record or array row data
Infer with typeof features; use StockFeatures only for deliberate stockFeatures typing.
Logical pinning does not style DOM direction automatically; use logical CSS insets. columnResizeDirection is unchanged.
v8
v9
Table enablePinning
enableColumnPinning plus enableRowPinning; column-level option remains
Combined sizing/resizing
columnSizingFeature plus columnResizingFeature for drag resizing
columnSizingInfo / onColumnSizingInfoChange
columnResizing / onColumnResizingChange
setColumnSizingInfo()
setColumnResizing()
sortingFn / sortingFns
sortFn / sortFns
getSortingFn() / getAutoSortingFn()
getSortFn() / getAutoSortFn()
SortingFn / SortingFns
SortFn / SortFns
row._getAllCellsByColumnId()
row.getAllCellsByColumnId()
table._getPinnedRows()
getTopRows(), getCenterRows(), or getBottomRows()
table._getFacetedRowModel()
Public faceting APIs on the relevant column/table
table._getFacetedMinMaxValues()
getFacetedMinMaxValues()
table._getFacetedUniqueValues()
getFacetedUniqueValues()
All other underscore-prefixed internals are removed. getIsSomeRowsSelected() and getIsSomePageRowsSelected() now stay true when all applicable rows are selected. Use getIsSomeRowsSelected() && !getIsAllRowsSelected() or getIsSomePageRowsSelected() && !getIsAllPageRowsSelected() for indeterminate UI.
Migration procedure
Replace React adapter imports and useReactTable; remove Table-only preact/compat configuration.
Inventory used features, row models, registries, state slices, methods, and _ internals.
Build tableFeatures() in prerequisite order; remove getCoreRowModel and old row-model placement.
Apply all pinning, sizing, sorting, row, and selection mappings above.
Update helpers/types with typeof features, retarget augmentation, and prefer feature registry/meta slots.
Replace getState()/onStateChange; explicitly choose internal, per-slice controlled, or external-atom ownership.
Replace unbound row/cell/column/header methods and migrate rendering.
Add tableOptions, subscriptions, or createTableHook only where reusable composition or render isolation needs them.
Type-check and exercise every enabled client- and server-side flow.
Audit away stockFeatures if the production table should remain tree-shakable.
Final migration checklist
Replace React-adapter imports/useReactTable with native Preact imports/useTable and remove Table-only compat aliases.
Register every used stock feature explicitly and put prerequisites before dependent slots.
Remove getCoreRowModel; move all eight optional row-model factories into tableFeatures.
Move filterFns, sortFns, aggregationFns, and filterMeta into feature slots.
Replace table.getState() and top-level onStateChange; choose Preact selectors, Subscribe, per-slice callbacks, store subscription, or external atoms.
Audit controlled/external ownership and atom precedence.
Replace unbound or copied row/cell/column/header methods.
Apply the entire left/right to start/end pinning map and logical sticky CSS.
Split pinning options and sizing/resizing; rename resizing state, setter, and callback.
Apply every sorting option/API/type/registry rename.
Remove all listed underscore-prefixed internals and use public replacements.
Pair each some-selected check with its all-selected predicate for indeterminate UI.
Update TFeatures helpers/types, columns(), StockFeatures, meta slots, registries, and RowData.
Migrate Preact FlexRender and adopt tableOptions/createTableHook only where useful.
Type-check and test every enabled client/manual feature plus LTR/RTL layout behavior.
Remove temporary stockFeatures after the explicit-feature audit when tree-shaking matters.
Common migration failures
Keeping React imports or compatibility aliases after adopting the native Preact adapter.
Registering a row model without its feature, or leaving it on table options.
Assuming table.atoms narrows parent renders while the default full selector is still active.
Supplying controlled state without its per-slice change handler.
Destructuring prototype-backed methods.
Renaming pinning state without updating sticky CSS and all region APIs.
Using the changed “some selected” semantics directly as checkbox indeterminate state.
API discovery
Inspect node_modules/@tanstack/preact-table/dist/index.d.ts and node_modules/@tanstack/table-core/dist/index.d.ts. Do not copy React adapter APIs merely because the v8 app used preact/compat.
1---2name: migrate-v8-to-v9-63description: Perform a complete Preact TanStack Table v8-to-v9 migration: remove React-through-preact/compat usage, adopt the native adapter, explicit features and row-model slots, Preact state and atoms, rendering, composition, types, and every shared API rename and semantic change. Use for migration plans, implementation, or audits.4---56Read `@tanstack/table-core#migrate-v8-to-v9`, `getting-started`, and `table-state`. Use this as the exhaustive Preact migration checklist. Verify the exact v9 APIs in the installed declarations before assuming a different v9 version has the same surface.78Framework prerequisite: Preact 10 or newer (`preact >=10`).910## Target architecture1112```tsx13import {14 createSortedRowModel,15 rowSortingFeature,16 tableFeatures,17 useTable,18} from '@tanstack/preact-table'1920const features = tableFeatures({21 rowSortingFeature,22 sortedRowModel: createSortedRowModel(),23})24const table = useTable({ features, columns, data })25```2627V8 did not have a first-party Preact adapter; many Preact apps used `@tanstack/react-table` through `preact/compat`. V9 uses native `@tanstack/preact-table`. Remove compatibility aliases that existed only for Table after replacing the imports. Prefer explicit features as the end state; `stockFeatures` is only a kitchen-sink migration shortcut.2829## Complete breaking-change map3031### Adapter, construction, and features3233| v8 | v9 |34| --------------------------------------------------------- | ------------------------------------------------------------------- |35| `@tanstack/react-table` through `preact/compat` | Native `@tanstack/preact-table` |36| `useReactTable(options)` | `useTable({ ...options, features })` |37| Every feature bundled | Register used `*Feature` objects with `tableFeatures()` |38| `getCoreRowModel()` option | Remove it; core is automatic |39| `getFilteredRowModel()` | `filteredRowModel: createFilteredRowModel()` |40| `getSortedRowModel()` | `sortedRowModel: createSortedRowModel()` |41| `getPaginationRowModel()` | `paginatedRowModel: createPaginatedRowModel()` |42| `getExpandedRowModel()` | `expandedRowModel: createExpandedRowModel()` |43| `getGroupedRowModel()` | `groupedRowModel: createGroupedRowModel()` |44| `getFacetedRowModel()` | `facetedRowModel: createFacetedRowModel()` |45| `getFacetedMinMaxValues()` | `facetedMinMaxValues: createFacetedMinMaxValues()` |46| `getFacetedUniqueValues()` | `facetedUniqueValues: createFacetedUniqueValues()` |47| Table/factory `sortingFns`, `filterFns`, `aggregationFns` | `sortFns`, `filterFns`, `aggregationFns` slots in `tableFeatures()` |48| Removed `rowModels: { ... }` object | Direct named slots in `tableFeatures()` |4950In the registry slots, register individually imported built-ins (`filterFn_includesString`, `sortFn_alphanumeric`, `aggregationFn_sum`, and so on) under their conventional keys alongside custom functions; the full `filterFns`/`sortFns`/`aggregationFns` registry objects still work but bundle every built-in.5152Register each prerequisite feature before its row-model slot. Stock features are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows.5354### Preact state and subscriptions5556| v8 | v9 |57| --------------------------- | ------------------------------------------------------------------------------------ |58| `table.getState()` | Reactive `table.state`, snapshot `table.store.state`, or `table.atoms.<slice>.get()` |59| Top-level `onStateChange` | Per-slice callbacks or `table.store.subscribe()` |60| Whole-component state reads | Custom second-argument selector, `table.Subscribe`, or atom source |6162The default `useTable` selector subscribes the component to all registered state. Pass a selector for `table.state`, or `() => null` and subscribe lower:6364```tsx65const table = useTable(options, () => null)6667<table.Subscribe source={table.atoms.rowSelection}>68 {selection => <span>{Object.keys(selection).length} selected</span>}69</table.Subscribe>70```7172Controlled `state` plus per-slice `on[State]Change` remains supported. For app-owned atoms, use `useCreateAtom`/`useSelector` from `@tanstack/preact-store` and pass them through `options.atoms`. An external atom wins over `state` for the same slice; do not mirror both ownership models.7374### Rendering and composition7576- Replace React-adapter `flexRender(def, context)` with `<table.FlexRender cell={cell} />` or standalone `<FlexRender ... />`; the function remains for advanced cases.77- Use `tableOptions()` for typed reusable option fragments.78- Use `createTableHook({ features, ...defaults })` for repeated app table conventions; it returns native app helpers such as `useAppTable` and `createAppColumnHelper`.79- Call row/cell/column/header methods on their instance. Prototype methods lose `this` when destructured and do not appear in object spread, `Object.keys`, or JSON. Table-instance methods are not affected.8081### TypeScript and helpers8283| v8 | v9 |84| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |85| `createColumnHelper<Person>()` | `createColumnHelper<typeof features, Person>()` |86| Plain arrays that widen nested values | `columnHelper.columns([...])` |87| `ColumnDef<TData>` | `ColumnDef<TFeatures, TData, TValue>` |88| `Column<TData>`, `Row<TData>`, `Table<TData>` | Add `TFeatures` first |89| `Cell<TData, TValue>` | `Cell<TFeatures, TData, TValue>` |90| React module augmentation | Target `@tanstack/preact-table` |91| `TableMeta<TData>` / `ColumnMeta<TData, TValue>` | Add `TFeatures`, or use per-table `tableMeta` / `columnMeta` slots with `metaHelper()` |92| Global `FilterFns`, `SortFns`, `AggregationFns`, and `FilterMeta` augmentation | `filterFns`, `sortFns`, `aggregationFns`, and `filterMeta` feature slots |93| `RowData = unknown` | Record or array row data |9495Infer with `typeof features`; use `StockFeatures` only for deliberate `stockFeatures` typing.9697### Shared API and semantic changes9899| v8 pinning | v9 |100| -------------------------------------------------------------- | ------------------------------------------------------------- |101| `columnPinning.left` / `.right` | `.start` / `.end` |102| `column.pin('left' \| 'right')` | `column.pin('start' \| 'end')` |103| `getIsPinned() === 'left' \| 'right'` | `'start' \| 'end'` |104| `row.getLeftVisibleCells()` / `getRightVisibleCells()` | `getStartVisibleCells()` / `getEndVisibleCells()` |105| `getLeftHeaderGroups()` / `getRightHeaderGroups()` | `getStartHeaderGroups()` / `getEndHeaderGroups()` |106| `getLeftFooterGroups()` / `getRightFooterGroups()` | `getStartFooterGroups()` / `getEndFooterGroups()` |107| `getLeftFlatHeaders()` / `getRightFlatHeaders()` | `getStartFlatHeaders()` / `getEndFlatHeaders()` |108| `getLeftLeafHeaders()` / `getRightLeafHeaders()` | `getStartLeafHeaders()` / `getEndLeafHeaders()` |109| `getLeftLeafColumns()` / `getRightLeafColumns()` | `getStartLeafColumns()` / `getEndLeafColumns()` |110| `getLeftVisibleLeafColumns()` / `getRightVisibleLeafColumns()` | `getStartVisibleLeafColumns()` / `getEndVisibleLeafColumns()` |111| `getLeftTotalSize()` / `getRightTotalSize()` | `getStartTotalSize()` / `getEndTotalSize()` |112| `column.getStart('left')` | `column.getStart('start')` |113| `column.getAfter('right')` | `column.getAfter('end')` |114| `column.getIndex('left' \| 'right')` | `column.getIndex('start' \| 'end')` |115116Logical pinning does not style DOM direction automatically; use logical CSS insets. `columnResizeDirection` is unchanged.117118| v8 | v9 |119| ----------------------------------------------- | -------------------------------------------------------------------------- |120| Table `enablePinning` | `enableColumnPinning` plus `enableRowPinning`; column-level option remains |121| Combined sizing/resizing | `columnSizingFeature` plus `columnResizingFeature` for drag resizing |122| `columnSizingInfo` / `onColumnSizingInfoChange` | `columnResizing` / `onColumnResizingChange` |123| `setColumnSizingInfo()` | `setColumnResizing()` |124| `sortingFn` / `sortingFns` | `sortFn` / `sortFns` |125| `getSortingFn()` / `getAutoSortingFn()` | `getSortFn()` / `getAutoSortFn()` |126| `SortingFn` / `SortingFns` | `SortFn` / `SortFns` |127| `row._getAllCellsByColumnId()` | `row.getAllCellsByColumnId()` |128| `table._getPinnedRows()` | `getTopRows()`, `getCenterRows()`, or `getBottomRows()` |129| `table._getFacetedRowModel()` | Public faceting APIs on the relevant column/table |130| `table._getFacetedMinMaxValues()` | `getFacetedMinMaxValues()` |131| `table._getFacetedUniqueValues()` | `getFacetedUniqueValues()` |132133All other underscore-prefixed internals are removed. `getIsSomeRowsSelected()` and `getIsSomePageRowsSelected()` now stay true when all applicable rows are selected. Use `getIsSomeRowsSelected() && !getIsAllRowsSelected()` or `getIsSomePageRowsSelected() && !getIsAllPageRowsSelected()` for indeterminate UI.134135## Migration procedure1361371. Replace React adapter imports and `useReactTable`; remove Table-only `preact/compat` configuration.1382. Inventory used features, row models, registries, state slices, methods, and `_` internals.1393. Build `tableFeatures()` in prerequisite order; remove `getCoreRowModel` and old row-model placement.1404. Apply all pinning, sizing, sorting, row, and selection mappings above.1415. Update helpers/types with `typeof features`, retarget augmentation, and prefer feature registry/meta slots.1426. Replace `getState()`/`onStateChange`; explicitly choose internal, per-slice controlled, or external-atom ownership.1437. Replace unbound row/cell/column/header methods and migrate rendering.1448. Add `tableOptions`, subscriptions, or `createTableHook` only where reusable composition or render isolation needs them.1459. Type-check and exercise every enabled client- and server-side flow.14610. Audit away `stockFeatures` if the production table should remain tree-shakable.147148## Final migration checklist149150- [ ] Replace React-adapter imports/useReactTable with native Preact imports/useTable and remove Table-only compat aliases.151- [ ] Register every used stock feature explicitly and put prerequisites before dependent slots.152- [ ] Remove `getCoreRowModel`; move all eight optional row-model factories into `tableFeatures`.153- [ ] Move `filterFns`, `sortFns`, `aggregationFns`, and `filterMeta` into feature slots.154- [ ] Replace `table.getState()` and top-level `onStateChange`; choose Preact selectors, Subscribe, per-slice callbacks, store subscription, or external atoms.155- [ ] Audit controlled/external ownership and atom precedence.156- [ ] Replace unbound or copied row/cell/column/header methods.157- [ ] Apply the entire left/right to start/end pinning map and logical sticky CSS.158- [ ] Split pinning options and sizing/resizing; rename resizing state, setter, and callback.159- [ ] Apply every sorting option/API/type/registry rename.160- [ ] Remove all listed underscore-prefixed internals and use public replacements.161- [ ] Pair each some-selected check with its all-selected predicate for indeterminate UI.162- [ ] Update TFeatures helpers/types, `columns()`, `StockFeatures`, meta slots, registries, and RowData.163- [ ] Migrate Preact FlexRender and adopt tableOptions/createTableHook only where useful.164- [ ] Type-check and test every enabled client/manual feature plus LTR/RTL layout behavior.165- [ ] Remove temporary stockFeatures after the explicit-feature audit when tree-shaking matters.166167## Common migration failures168169- Keeping React imports or compatibility aliases after adopting the native Preact adapter.170- Registering a row model without its feature, or leaving it on table options.171- Assuming `table.atoms` narrows parent renders while the default full selector is still active.172- Supplying controlled state without its per-slice change handler.173- Destructuring prototype-backed methods.174- Renaming pinning state without updating sticky CSS and all region APIs.175- Using the changed “some selected” semantics directly as checkbox indeterminate state.176177## API discovery178179Inspect `node_modules/@tanstack/preact-table/dist/index.d.ts` and `node_modules/@tanstack/table-core/dist/index.d.ts`. Do not copy React adapter APIs merely because the v8 app used `preact/compat`.
Run npx skillmds@latest add tanstack/migrate-v8-to-v9-6 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Perform a complete Preact TanStack Table v8-to-v9 migration: remove React-through-preact/compat usage, adopt the native adapter, explicit features and row-model slots, Preact state and atoms, rendering, composition, types, and every shared API rename and semantic change. Use for migration plans, implementation, or audits. It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
TanStack (@tanstack) published this skill. Their other Agent Skills are listed on their SkillMD profile.