Migrating to v4
What changes when you upgrade tablecraft to v4 and TanStack Table v9.
tablecraft v4 targets TanStack Table v9. There are five changes to make.
1. Upgrade the peer dependency
npm install @marvinackerman/tablecraft@^4 @tanstack/react-table@^9Staying on v8 is supported: tablecraft 3.x remains published and installable.
2. Column pinning uses start/end
- const { pinLeft, pinRight, leftColumns, rightColumns } = columnPinning
+ const { pinStart, pinEnd, startColumns, endColumns } = columnPinningisPinned returns 'start' | 'end' | false. The rename comes from v9 and is a
correctness fix — "left" is wrong in a right-to-left layout. If you position
pinned columns with CSS, inset-inline-start and inset-inline-end now match
the vocabulary.
3. Type instances with TablecraftTable
- import type { Table } from '@tanstack/react-table'
- const table: Table<User> = useTable({ data, columns }).table
+ import type { TablecraftTable } from '@marvinackerman/tablecraft'
+ const table: TablecraftTable<User> = useTable({ data, columns }).tablev9's Table takes a feature set as its first type parameter. tablecraft binds
that set for you, so you keep writing a single type argument.
4. Explicit ColumnDef/FilterFn annotations need TablecraftFeatures
v9's ColumnDef and FilterFn take a feature set as their first type
parameter, the same way Table does above. tablecraft's own hooks already
bind that parameter internally, so this only matters if you write the
annotation out yourself instead of letting it be inferred:
- const columns: ColumnDef<User, any>[] = [...]
+ import type { TablecraftFeatures } from '@marvinackerman/tablecraft'
+ const columns: ColumnDef<TablecraftFeatures, User, any>[] = [...]The same applies to a hand-written fuzzy filter function:
- const exactMatch: FilterFn<User> = (row, columnId, filterValue) => ...
+ import type { TablecraftFeatures } from '@marvinackerman/tablecraft'
+ const exactMatch: FilterFn<TablecraftFeatures, User> = (row, columnId, filterValue) => ...Most consumers rely on inference — createColumns<User>([...]) or
useTable<User>({...}) — and never write either annotation, so this change
does not touch them. It hits only code that spelled out ColumnDef<User, ...>
or FilterFn<User> explicitly.
5. columnsFromZod requires an object or array schema
v9 tightened RowData from unknown to Record<string, any> | Array<any>, and
columnsFromZod follows: its schema parameter is now z.ZodType<RowData>.
A normal object schema is unaffected — this is the overwhelmingly common case:
// still fine
columnsFromZod(z.object({ id: z.number(), name: z.string() }))A schema whose output is a primitive is now rejected at compile time:
// was: type-checked, then threw at runtime
// now: fails to compile
columnsFromZod(z.string())Nothing that worked before stops working. The change converts a runtime failure into a compile-time one.
What does not change
Every hook name, every option and every return shape. useTable,
useServerTable, useQueryTable, the ten granular state hooks, editing,
export, faceting and accessibility are all called exactly as before. The one
exception is change 4 above: if you annotate ColumnDef or FilterFn
explicitly rather than relying on inference, that annotation's type changes.
Two things from v9 itself
These are TanStack renames rather than tablecraft ones, but you will meet them on the way through.
RowData is narrower. v9 tightened it from unknown to
Record<string, any> | Array<any>. If your row type is a primitive, wrap it in
an object.
VisibilityState is now ColumnVisibilityState. tablecraft does not
re-export it, so if you imported it from @tanstack/react-table to type a
visibility map, rename the import:
- import type { VisibilityState } from '@tanstack/react-table'
+ import type { ColumnVisibilityState } from '@tanstack/react-table'