createColumnHelper
function createColumnHelper<TData extends RowData>(): ColumnHelper<TData>
Returns a type-safe column definition builder. The helper provides four methods:
.accessor(accessorKeyOrFn, options)
Define a column that reads data from each row.
Key accessor:
const col = columnHelper.accessor('name', {
header: 'Name',
enableSorting: true,
})
// Infers: accessorKey = 'name', value type = TData['name']
Function accessor:
const col = columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
id: 'fullName',
header: 'Full Name',
})
.display(options)
Define a non-data column (actions, checkboxes, etc.). Requires an id.
const col = columnHelper.display({
id: 'actions',
header: 'Actions',
cell: ({ row }) => `<button>Edit ${row.original.name}</button>`,
})
.group(options)
Define a column group header that contains child columns.
const col = columnHelper.group({
id: 'contact',
header: 'Contact Info',
columns: [
columnHelper.accessor('email', { header: 'Email' }),
columnHelper.accessor('phone', { header: 'Phone' }),
],
})
.columns(columnList)
Normalize a heterogeneous column list into ColumnDef<TData, unknown>[].
Each .accessor(...) returns a ColumnDef<TData, TValue> with a concrete,
per-column value type. Because TValue is invariant, an inline array mixing
string, number, and boolean columns infers as a union that does not assign to
ColumnDef<TData, unknown>[], forcing an as ColumnDef<TData, unknown> cast on
nearly every column. Wrap the array in .columns([...]) to erase the per-column
TValue in one place:
const columns = columnHelper.columns([
columnHelper.accessor('name', { header: 'Name' }), // TValue = string
columnHelper.accessor('age', { header: 'Age' }), // TValue = number
columnHelper.accessor('active', { header: 'Active' }), // TValue = boolean
columnHelper.display({ id: 'actions', header: 'Actions' }),
])
// columns: ColumnDef<Person, unknown>[] — no per-column casts
It returns a fresh array (safe to mutate) and preserves element identity and order.