v9.0.0-beta.10 introduces a breaking change in how row models are defined in order to bring increased type-safety features. Row model factories and function registries now live as slots on the features object instead of a separate rowModels option, and the factories no longer take arguments. If you migrated on an earlier beta, see the Row Model Factories section below for the new shape.
TanStack Table v9 is a major release that makes table setup more explicit and more tree-shakeable. The Solid adapter keeps the same headless rendering model, but table creation, row model registration, state reads, and rendering helpers have changed.
The main migration is replacing createSolidTable with createTable, then moving feature and row-model setup into the v9 shape.
// v8
import { createSolidTable } from '@tanstack/solid-table'
const table = createSolidTable(options)
// v9
import { createTable } from '@tanstack/solid-table'
const table = createTable(options)// v8
import { createSolidTable } from '@tanstack/solid-table'
const table = createSolidTable(options)
// v9
import { createTable } from '@tanstack/solid-table'
const table = createTable(options)// v8
import {
createSolidTable,
getCoreRowModel,
} from '@tanstack/solid-table'
const table = createSolidTable({
columns,
get data() {
return data()
},
getCoreRowModel: getCoreRowModel(),
})
// v9
import { createTable, tableFeatures } from '@tanstack/solid-table'
const features = tableFeatures({})
const table = createTable({
features,
columns,
get data() {
return data()
},
})// v8
import {
createSolidTable,
getCoreRowModel,
} from '@tanstack/solid-table'
const table = createSolidTable({
columns,
get data() {
return data()
},
getCoreRowModel: getCoreRowModel(),
})
// v9
import { createTable, tableFeatures } from '@tanstack/solid-table'
const features = tableFeatures({})
const table = createTable({
features,
columns,
get data() {
return data()
},
})Keep features and column definitions outside reactive component work when they are static.
Features control which APIs, options, and state slices exist on the table.
import {
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
tableFeatures,
} from '@tanstack/solid-table'
const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
})import {
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
tableFeatures,
} from '@tanstack/solid-table'
const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
})If a feature is not registered, its APIs and state slice are not available. For example, table.atoms.rowSelection requires rowSelectionFeature.
stockFeatures is useful when you want a quick v8-like migration path before auditing features.
import { createTable, stockFeatures } from '@tanstack/solid-table'
const table = createTable({
features: stockFeatures,
columns,
get data() {
return data()
},
})import { createTable, stockFeatures } from '@tanstack/solid-table'
const table = createTable({
features: stockFeatures,
columns,
get data() {
return data()
},
})Use it as a migration shortcut, not as the preferred production end state.
| Feature | Import Name |
|---|---|
| Column Filtering | columnFilteringFeature |
| Global Filtering | globalFilteringFeature |
| Row Sorting | rowSortingFeature |
| Row Pagination | rowPaginationFeature |
| Row Selection | rowSelectionFeature |
| Row Expanding | rowExpandingFeature |
| Row Pinning | rowPinningFeature |
| Column Pinning | columnPinningFeature |
| Column Visibility | columnVisibilityFeature |
| Column Ordering | columnOrderingFeature |
| Column Sizing | columnSizingFeature |
| Column Resizing | columnResizingFeature |
| Column Grouping | columnGroupingFeature |
| Column Faceting | columnFacetingFeature |
Row model factories now live inside tableFeatures alongside feature objects. Function registries (filterFns, sortFns, aggregationFns) are also passed as slots in tableFeatures rather than as arguments to the factory functions.
| v8 Option | v9 tableFeatures Key | v9 Factory Function |
|---|---|---|
| getCoreRowModel() | (automatic) | Not needed |
| getFilteredRowModel() | filteredRowModel | createFilteredRowModel() |
| getSortedRowModel() | sortedRowModel | createSortedRowModel() |
| getPaginationRowModel() | paginatedRowModel | createPaginatedRowModel() |
| getExpandedRowModel() | expandedRowModel | createExpandedRowModel() |
| getGroupedRowModel() | groupedRowModel | createGroupedRowModel() |
| getFacetedRowModel() | facetedRowModel | createFacetedRowModel() |
| getFacetedMinMaxValues() | facetedMinMaxValues | createFacetedMinMaxValues() |
| getFacetedUniqueValues() | facetedUniqueValues | createFacetedUniqueValues() |
// v8
import {
createSolidTable,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
filterFns,
sortingFns,
} from '@tanstack/solid-table'
const table = createSolidTable({
columns,
get data() {
return data()
},
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
filterFns,
sortingFns,
})
// v9
import {
columnFilteringFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
createTable,
filterFns,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/solid-table'
const features = tableFeatures({
columnFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
sortedRowModel: createSortedRowModel(),
paginatedRowModel: createPaginatedRowModel(),
filterFns,
sortFns,
})
const table = createTable({
features,
columns,
get data() {
return data()
},
})// v8
import {
createSolidTable,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
filterFns,
sortingFns,
} from '@tanstack/solid-table'
const table = createSolidTable({
columns,
get data() {
return data()
},
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
filterFns,
sortingFns,
})
// v9
import {
columnFilteringFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
createTable,
filterFns,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/solid-table'
const features = tableFeatures({
columnFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
sortedRowModel: createSortedRowModel(),
paginatedRowModel: createPaginatedRowModel(),
filterFns,
sortFns,
})
const table = createTable({
features,
columns,
get data() {
return data()
},
})Solid v9 uses table atoms backed by Solid primitives. Prefer narrow atom reads or Solid memos over broad whole-state reads.
| Surface | Use |
|---|---|
| table.atoms.<slice>.get() | Narrow reactive reads inside Solid tracking scopes. |
| table.store.get() | Current full state snapshot. Use mostly for debug output or intentionally broad dependencies. |
| table.Subscribe | A Solid render boundary whose child reads the atoms it needs. |
| table.baseAtoms.<slice> | Internal writable atoms. Prefer feature APIs or external atoms. |
// v8
const sorting = table.getState().sorting
// v9: narrow atom read
const sorting = table.atoms.sorting.get()
// v9: full snapshot
const tableState = table.store.get()// v8
const sorting = table.getState().sorting
// v9: narrow atom read
const sorting = table.atoms.sorting.get()
// v9: full snapshot
const tableState = table.store.get()Use Solid primitives to derive reactive values:
import { createMemo } from 'solid-js'
const pagination = createMemo(() => table.atoms.pagination.get())
const pageIndex = createMemo(() => pagination().pageIndex)
const tableStateJson = createMemo(() =>
JSON.stringify(table.store.get(), null, 2),
)import { createMemo } from 'solid-js'
const pagination = createMemo(() => table.atoms.pagination.get())
const pageIndex = createMemo(() => pagination().pageIndex)
const tableStateJson = createMemo(() =>
JSON.stringify(table.store.get(), null, 2),
)Atom reads can also be used directly in JSX:
<span>
Page {table.atoms.pagination.get().pageIndex + 1} of {table.getPageCount()}
</span><span>
Page {table.atoms.pagination.get().pageIndex + 1} of {table.getPageCount()}
</span>table.Subscribe passes table.atoms to its child function. As with any Solid component, the child function body runs once and is untracked, so read atoms inside JSX expressions (or thunks called from JSX) for Solid to track them.
<table.Subscribe>
{(atoms) => (
<span>Page {atoms.pagination.get().pageIndex + 1}</span>
)}
</table.Subscribe><table.Subscribe>
{(atoms) => (
<span>Page {atoms.pagination.get().pageIndex + 1}</span>
)}
</table.Subscribe>The v8-style state + on[State]Change controlled state patterns still work and remain convenient for simple integrations. For new v9 code, prefer owning state slices with external atoms (see External Atoms below), which give you fine-grained subscriptions without mirroring state through signals. If you do control state with signals, use getters in state so Solid tracks the current signal values.
import { createSignal } from 'solid-js'
import type { PaginationState, SortingState } from '@tanstack/solid-table'
const [sorting, setSorting] = createSignal<SortingState>([])
const [pagination, setPagination] = createSignal<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const table = createTable({
features,
columns,
get data() {
return data()
},
state: {
get sorting() {
return sorting()
},
get pagination() {
return pagination()
},
},
onSortingChange: setSorting,
onPaginationChange: setPagination,
})import { createSignal } from 'solid-js'
import type { PaginationState, SortingState } from '@tanstack/solid-table'
const [sorting, setSorting] = createSignal<SortingState>([])
const [pagination, setPagination] = createSignal<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const table = createTable({
features,
columns,
get data() {
return data()
},
state: {
get sorting() {
return sorting()
},
get pagination() {
return pagination()
},
},
onSortingChange: setSorting,
onPaginationChange: setPagination,
})The v8-style top-level onStateChange callback is gone. Use per-slice on[State]Change handlers or external atoms.
External atoms are useful when the app should own a table state slice outside one component.
import { createAtom, useSelector } from '@tanstack/solid-store'
import type { PaginationState, SortingState } from '@tanstack/solid-table'
const sortingAtom = createAtom<SortingState>([])
const paginationAtom = createAtom<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
function MyTable() {
const pagination = useSelector(paginationAtom)
const table = createTable({
features,
columns,
get data() {
return data()
},
atoms: {
sorting: sortingAtom,
pagination: paginationAtom,
},
})
return <span>Page {pagination().pageIndex + 1}</span>
}import { createAtom, useSelector } from '@tanstack/solid-store'
import type { PaginationState, SortingState } from '@tanstack/solid-table'
const sortingAtom = createAtom<SortingState>([])
const paginationAtom = createAtom<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
function MyTable() {
const pagination = useSelector(paginationAtom)
const table = createTable({
features,
columns,
get data() {
return data()
},
atoms: {
sorting: sortingAtom,
pagination: paginationAtom,
},
})
return <span>Page {pagination().pageIndex + 1}</span>
}Do not provide both atoms.pagination and state.pagination; the atom owns that slice.
Column helpers and column types now include TFeatures first.
// v8
const columnHelper = createColumnHelper<Person>()
const columns: ColumnDef<Person>[] = [
columnHelper.accessor('age', {
header: 'Age',
sortingFn: 'alphanumeric',
}),
]
// v9
const columnHelper = createColumnHelper<typeof features, Person>()
const columns: Array<ColumnDef<typeof features, Person>> = columnHelper.columns([
columnHelper.accessor('age', {
header: 'Age',
sortFn: 'alphanumeric',
}),
])// v8
const columnHelper = createColumnHelper<Person>()
const columns: ColumnDef<Person>[] = [
columnHelper.accessor('age', {
header: 'Age',
sortingFn: 'alphanumeric',
}),
]
// v9
const columnHelper = createColumnHelper<typeof features, Person>()
const columns: Array<ColumnDef<typeof features, Person>> = columnHelper.columns([
columnHelper.accessor('age', {
header: 'Age',
sortFn: 'alphanumeric',
}),
])Use columnHelper.columns([...]) to keep TValue inference across nested column definitions.
Replace flexRender(def, context) calls with FlexRender.
// v8
<th>{flexRender(header.column.columnDef.header, header.getContext())}</th>
// v9
<th>
<FlexRender header={header} />
</th>// v8
<th>{flexRender(header.column.columnDef.header, header.getContext())}</th>
// v9
<th>
<FlexRender header={header} />
</th>The table instance also exposes the same component:
<table.FlexRender header={header} />
<table.FlexRender cell={cell} />
<table.FlexRender footer={header} /><table.FlexRender header={header} />
<table.FlexRender cell={cell} />
<table.FlexRender footer={header} />tableOptions() lets you compose reusable option fragments with type inference.
import { tableOptions } from '@tanstack/solid-table'
const baseOptions = tableOptions({
features,
defaultColumn: {
minSize: 40,
},
})
const table = createTable({
...baseOptions,
columns,
get data() {
return data()
},
})import { tableOptions } from '@tanstack/solid-table'
const baseOptions = tableOptions({
features,
defaultColumn: {
minSize: 40,
},
})
const table = createTable({
...baseOptions,
columns,
get data() {
return data()
},
})createTableHook creates shared Solid table helpers.
import { createTableHook } from '@tanstack/solid-table'
const { createAppTable, createAppColumnHelper } = createTableHook({
features,
})
const columnHelper = createAppColumnHelper<Person>()
const table = createAppTable({
columns,
get data() {
return data()
},
})import { createTableHook } from '@tanstack/solid-table'
const { createAppTable, createAppColumnHelper } = createTableHook({
features,
})
const columnHelper = createAppColumnHelper<Person>()
const table = createAppTable({
columns,
get data() {
return data()
},
})See the Composable Tables Guide for complete patterns.
Table-level enablePinning split into:
enableColumnPinning: true
enableRowPinning: trueenableColumnPinning: true
enableRowPinning: trueColumn resizing is now a separate feature and state slice.
const features = tableFeatures({
columnSizingFeature,
columnResizingFeature,
})const features = tableFeatures({
columnSizingFeature,
columnResizingFeature,
})columnSizingInfo became columnResizing, and onColumnSizingInfoChange became onColumnResizingChange. The setColumnSizingInfo() API became setcolumnResizing() (note the lowercase c, the current v9 spelling).
| v8 | v9 |
|---|---|
| sortingFn | sortFn |
| sortingFns | sortFns |
| getSortingFn() | getSortFn() |
| getAutoSortingFn() | getAutoSortFn() |
| SortingFn | SortFn |
Underscore-prefixed APIs that are now public should be called without _, such as row.getAllCellsByColumnId().
Use TFeatures as the first generic:
ColumnDef<typeof features, Person>
Column<typeof features, Person>
Row<typeof features, Person>
Table<typeof features, Person>ColumnDef<typeof features, Person>
Column<typeof features, Person>
Row<typeof features, Person>
Table<typeof features, Person>const features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
})
const columnHelper = createColumnHelper<typeof features, Person>()const features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
})
const columnHelper = createColumnHelper<typeof features, Person>()import type { StockFeatures } from '@tanstack/solid-table'
type PersonColumn = ColumnDef<StockFeatures, Person>import type { StockFeatures } from '@tanstack/solid-table'
type PersonColumn = ColumnDef<StockFeatures, Person>No more declaration merging required! (Although it still works if you want to keep using it)
Global declaration merging works exactly like it did in v8. The only change you need to make is updating the generics shape: both interfaces now take TFeatures as the first type parameter.
declare module '@tanstack/solid-table' {
interface ColumnMeta<TFeatures, TData, TValue> {
align?: 'left' | 'right'
}
}declare module '@tanstack/solid-table' {
interface ColumnMeta<TFeatures, TData, TValue> {
align?: 'left' | 'right'
}
}That's all that's required if you want to keep declaring meta types globally.
Optionally, v9 also adds a new way to declare meta types per-table without declaration merging. You can use type-only tableMeta/columnMeta slots on the features option, which only affect tables created with that features object:
const features = tableFeatures({
rowSortingFeature,
columnMeta: metaHelper<{ align?: 'left' | 'right' }>(),
})const features = tableFeatures({
rowSortingFeature,
columnMeta: metaHelper<{ align?: 'left' | 'right' }>(),
})See the new Table and Column Meta Guide for full details on both approaches.
In v8, making a custom function usable as a string reference (like filterFn: 'fuzzy') required declare module augmentation of the FilterFns interface, and typing filter meta required augmenting FilterMeta. In v9, registering the function in the matching registry slot does both jobs with no global augmentation:
// v8
declare module '@tanstack/solid-table' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
// v9 - register in the slot; the key becomes a valid string value
interface FuzzyFilterMeta {
itemRank?: RankingInfo
}
const features = tableFeatures({
columnFilteringFeature,
filteredRowModel: createFilteredRowModel(),
filterFns: { ...filterFns, fuzzy: fuzzyFilter },
filterMeta: metaHelper<FuzzyFilterMeta>(),
})
// 'fuzzy' now typechecks in column defs for tables using these features
columnHelper.accessor('name', { filterFn: 'fuzzy' })// v8
declare module '@tanstack/solid-table' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
// v9 - register in the slot; the key becomes a valid string value
interface FuzzyFilterMeta {
itemRank?: RankingInfo
}
const features = tableFeatures({
columnFilteringFeature,
filteredRowModel: createFilteredRowModel(),
filterFns: { ...filterFns, fuzzy: fuzzyFilter },
filterMeta: metaHelper<FuzzyFilterMeta>(),
})
// 'fuzzy' now typechecks in column defs for tables using these features
columnHelper.accessor('name', { filterFn: 'fuzzy' })The same pattern applies to sortFns (for sortFn string values) and aggregationFns (for aggregationFn string values). See the Fuzzy Filtering Guide for a complete example.
Prefer explicit object row types:
type Person = {
firstName: string
lastName: string
age: number
}type Person = {
firstName: string
lastName: string
age: number
}