TanStack Table exposes a few small helper functions for authoring table configuration with better TypeScript inference. They do not add behavior by themselves. Instead, they help TypeScript preserve the relationship between your feature set, row type, column definitions, table options, and named function registries.
The most common helpers are:
tableFeatures - defines the static feature set and feature-related slots for a table
tableOptions - preserves inference when composing reusable table options
createColumnHelper - creates strongly typed column definitions
These helpers are exported from every framework adapter and from @tanstack/table-core.
Use tableFeatures() to declare which features a table uses. Features are opt-in in v9, so the object you pass to this helper determines which feature APIs and state slices exist on the table, columns, rows, headers, and cells.
For a basic table that only needs core behavior, pass an empty object:
import { tableFeatures } from '@tanstack/react-table'
const features = tableFeatures({})Add feature objects and row model factories when your table needs extra behavior:
import {
columnFilteringFeature,
createFilteredRowModel,
createSortedRowModel,
filterFns,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/react-table'
const features = tableFeatures({
columnFilteringFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
sortedRowModel: createSortedRowModel(),
filterFns,
sortFns,
})Keep features stable. In most apps, define it outside your component or in shared table setup code. Since typeof features is used throughout the table's types, stable features also make it easier to reuse column helpers, column definitions, and shared options.
tableFeatures() is the static configuration point for things that are stitched into a table before it is created:
Feature objects such as rowSortingFeature, columnFilteringFeature, or custom features
Row model factories such as createSortedRowModel() and createFilteredRowModel()
Function registries such as filterFns, sortFns, and aggregationFns
Type-only slots such as tableMeta and columnMeta, usually declared with metaHelper
metaHelper() is useful when declaring type-only meta slots in tableFeatures(). It is equivalent to writing {} as MyMeta, but makes the intent clearer and avoids lint rules accidentally removing the type assertion:
import { metaHelper, rowSortingFeature, tableFeatures } from '@tanstack/react-table'
type MyTableMeta = {
updateData: (rowIndex: number, columnId: string, value: unknown) => void
}
type MyColumnMeta = {
align?: 'left' | 'right'
}
const features = tableFeatures({
rowSortingFeature,
tableMeta: metaHelper<MyTableMeta>(),
columnMeta: metaHelper<MyColumnMeta>(),
})For more detail on tableMeta, columnMeta, and metaHelper, see the Table and Column Meta Guide.
Function registry keys become valid string references in table and column options:
import {
createColumnHelper,
createSortedRowModel,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/react-table'
type Person = {
rank: number
}
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
sortFns: {
...sortFns,
byRank: (rowA, rowB, columnId) => {
return rowA.getValue<number>(columnId) - rowB.getValue<number>(columnId)
},
},
})
const columnHelper = createColumnHelper<typeof features, Person>()
const columns = columnHelper.columns([
columnHelper.accessor('rank', {
sortFn: 'byRank',
}),
])For more detail on row model slots and function registries, see the Row Models Guide.
Use tableOptions() when you want to compose reusable table option objects while keeping TanStack Table's generic inference intact. The helper returns the same object at runtime; its value is in the TypeScript overloads.
This is useful for shared defaults:
import { tableFeatures, tableOptions, useTable } from '@tanstack/react-table'
const features = tableFeatures({})
const sharedOptions = tableOptions({
features,
defaultColumn: {
minSize: 80,
maxSize: 400,
},
debugTable: true,
})
const table = useTable({
...sharedOptions,
columns,
data,
})It is also useful when a wrapper or table factory supplies required options later. For example, a reusable option fragment can omit data, columns, or features and still keep the remaining table option types connected to the same feature set and row type:
import {
createSortedRowModel,
rowSortingFeature,
tableFeatures,
tableOptions,
useTable,
} from '@tanstack/react-table'
type Person = {
firstName: string
lastName: string
age: number
}
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
})
const sortableDefaults = tableOptions<typeof features, Person>({
initialState: {
sorting: [{ id: 'lastName', desc: false }],
},
})
const table = useTable({
features,
columns,
data,
...sortableDefaults,
})Use tableOptions() for reusable configuration. For one-off table setup, passing the object directly to your adapter's table creation function is usually enough.
createColumnHelper() creates a small set of utilities for authoring column definitions:
accessor creates data columns from an accessor key or accessor function
display creates display-only columns such as actions or selection controls
group creates parent columns that contain nested columns
columns wraps arrays of column definitions to preserve each column's individual value type
The helper is typed by both your feature set and row type:
import { createColumnHelper, tableFeatures } from '@tanstack/react-table'
type Person = {
firstName: string
lastName: string
age: number
}
const features = tableFeatures({})
const columnHelper = createColumnHelper<typeof features, Person>()
const columns = columnHelper.columns([
columnHelper.accessor('firstName', {
header: 'First Name',
}),
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
header: 'Last Name',
}),
columnHelper.display({
id: 'actions',
cell: (info) => `Actions for ${info.row.id}`,
}),
])At runtime, the column helper only returns column definition objects. Its main purpose is to infer accessor values, feature-specific column options, and nested column arrays more precisely than plain object literals can in some TypeScript scenarios.
Column definitions have their own guide with the full details on accessor columns, display columns, grouping columns, IDs, and rendering. See the Column Defs Guide.
Use tableFeatures() for every v9 table. Even an empty tableFeatures({}) call clearly declares that the table only uses core features.
Use tableOptions() when options are shared, composed, or supplied in multiple steps. Skip it when the options object is only passed directly to useTable, createTable, injectTable, TableController, or constructTable.
Use createColumnHelper() when you want strong column inference, especially for accessor values, custom feature options, and nested column groups. Plain ColumnDef objects are still valid when they are easier for your use case.