# Migrate V8 To V9

> 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.

- Skill: `tanstack/migrate-v8-to-v9-6` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tanstack/migrate-v8-to-v9-6`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tanstack/migrate-v8-to-v9-6/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: TanStack (https://skillmd.com/u/tanstack)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tanstack/migrate-v8-to-v9-6

---


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

```tsx
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()`             |
| `getCoreRowModel()` option                                | Remove it; core is automatic                                        |
| `getFilteredRowModel()`                                   | `filteredRowModel: createFilteredRowModel()`                        |
| `getSortedRowModel()`                                     | `sortedRowModel: createSortedRowModel()`                            |
| `getPaginationRowModel()`                                 | `paginatedRowModel: createPaginatedRowModel()`                      |
| `getExpandedRowModel()`                                   | `expandedRowModel: createExpandedRowModel()`                        |
| `getGroupedRowModel()`                                    | `groupedRowModel: createGroupedRowModel()`                          |
| `getFacetedRowModel()`                                    | `facetedRowModel: createFacetedRowModel()`                          |
| `getFacetedMinMaxValues()`                                | `facetedMinMaxValues: createFacetedMinMaxValues()`                  |
| `getFacetedUniqueValues()`                                | `facetedUniqueValues: createFacetedUniqueValues()`                  |
| Table/factory `sortingFns`, `filterFns`, `aggregationFns` | `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:

```tsx
const table = useTable(options, () => null)

<table.Subscribe source={table.atoms.rowSelection}>
  {selection => <span>{Object.keys(selection).length} selected</span>}
</table.Subscribe>
```

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.

### Shared API and semantic changes

| v8 pinning                                                     | v9                                                            |
| -------------------------------------------------------------- | ------------------------------------------------------------- |
| `columnPinning.left` / `.right`                                | `.start` / `.end`                                             |
| `column.pin('left' \| 'right')`                                | `column.pin('start' \| 'end')`                                |
| `getIsPinned() === 'left' \| 'right'`                          | `'start' \| 'end'`                                            |
| `row.getLeftVisibleCells()` / `getRightVisibleCells()`         | `getStartVisibleCells()` / `getEndVisibleCells()`             |
| `getLeftHeaderGroups()` / `getRightHeaderGroups()`             | `getStartHeaderGroups()` / `getEndHeaderGroups()`             |
| `getLeftFooterGroups()` / `getRightFooterGroups()`             | `getStartFooterGroups()` / `getEndFooterGroups()`             |
| `getLeftFlatHeaders()` / `getRightFlatHeaders()`               | `getStartFlatHeaders()` / `getEndFlatHeaders()`               |
| `getLeftLeafHeaders()` / `getRightLeafHeaders()`               | `getStartLeafHeaders()` / `getEndLeafHeaders()`               |
| `getLeftLeafColumns()` / `getRightLeafColumns()`               | `getStartLeafColumns()` / `getEndLeafColumns()`               |
| `getLeftVisibleLeafColumns()` / `getRightVisibleLeafColumns()` | `getStartVisibleLeafColumns()` / `getEndVisibleLeafColumns()` |
| `getLeftTotalSize()` / `getRightTotalSize()`                   | `getStartTotalSize()` / `getEndTotalSize()`                   |
| `column.getStart('left')`                                      | `column.getStart('start')`                                    |
| `column.getAfter('right')`                                     | `column.getAfter('end')`                                      |
| `column.getIndex('left' \| 'right')`                           | `column.getIndex('start' \| 'end')`                           |

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

1. Replace React adapter imports and `useReactTable`; remove Table-only `preact/compat` configuration.
2. Inventory used features, row models, registries, state slices, methods, and `_` internals.
3. Build `tableFeatures()` in prerequisite order; remove `getCoreRowModel` and old row-model placement.
4. Apply all pinning, sizing, sorting, row, and selection mappings above.
5. Update helpers/types with `typeof features`, retarget augmentation, and prefer feature registry/meta slots.
6. Replace `getState()`/`onStateChange`; explicitly choose internal, per-slice controlled, or external-atom ownership.
7. Replace unbound row/cell/column/header methods and migrate rendering.
8. Add `tableOptions`, subscriptions, or `createTableHook` only where reusable composition or render isolation needs them.
9. Type-check and exercise every enabled client- and server-side flow.
10. 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`.

