TanStack
Getting Started

Migrating to TanStack Table V9 (Svelte)

What's New in TanStack Table V9

TanStack Table V9 delivers major performance improvements, hundreds of bug fixes, new and refreshed features, and optional helpers for composing and managing tables. Despite the scale of the release, the headless model, core table logic, column definitions, and rendering patterns remain familiar. Here are the key changes:

1. Better Performance

  • Lower memory usage: The core architecture now shares more behavior across table objects, with some large-table scenarios seeing up to 90% memory savings.
  • Faster client-side row models: Sorting, filtering, and aggregation paths have improved algorithms and memoization, with many scenarios seeing up to 40-70% speed improvements.
  • Better column resizing performance: Column resizing also gets significant performance improvements from the same architectural and memoization work.

2. State Management Overhaul

  • TanStack Store foundation: Table state is backed by TanStack Store atoms.
  • Svelte 5 reactivity: The adapter uses Svelte 5 runes and Svelte-aware atom bindings.
  • Native fine-grained reads: Use table.atoms.<slice>.get() for narrow state and native $derived values for projections. Use table.store.get() when a computation intentionally needs the complete state.

3. Type-Safety Improvements

  • New and revamped type helpers: New type helpers help define columns, custom filters, sorts, aggregations, column and table meta, shared table options and components, and more.
  • Per-table meta types: tableMeta, columnMeta, and filterMeta slots let you type meta for a specific table instead of globally augmenting shared interfaces. No more global declaration merging required!
  • Feature-gated APIs: APIs only exist when their feature is registered, and tableFeatures() validates feature prerequisites at the type level.

4. Tree Shaking and Extensibility

  • Import only the features you use: Tables that only need sorting do not ship filtering, pagination, or other unused feature code.
  • Tree-shakeable row models and functions: Row model factories and filterFns / sortFns / aggregationFns now live on tableFeatures(), so unused processing code can be dropped.
  • Custom features use the same system: Your own feature plugins can register state, options, and APIs alongside the built-in features. See the Custom Features Guide.

5. Composability

  • tableOptions(): Compose reusable table configuration, including features, row models, and default options.
  • createTableHook(): Define shared Svelte table factories with pre-bound features, row models, defaults, and registered components.

6. New and Refreshed Features

  • New Features
    • Cell Selection: cellSelectionFeature adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the Cell Selection Guide.
    • Cell Spanning: cellSpanningFeature merges body cells across rows and columns (spanRows / spanColumns, with span-aware cell selection), and header groups now compute header.rowSpan so shallow columns can span header rows. See the Cell Spanning Guide.
  • Refreshed Features
    • More capable features: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical start/end pinning, and more).
    • New core APIs: New table and row APIs (like table.getMaxSubRowDepth(), row.getDisplayIndex()) round out the core feature set.

The Good News: Most Table Logic Is Still Familiar

  • Column definitions keep the same basic accessorKey, accessorFn, header, cell, and footer shapes.
  • Feature APIs like table.nextPage(), column.toggleSorting(), and row.toggleSelected() remain the preferred way to change state.
  • Markup still renders header groups, rows, and cells from the table instance.

The main changes are the Svelte 5 requirement, the new createTable entrypoint, explicit features (including row models registered as feature slots), and the move from v8 writable-store patterns to v9 runes and atoms.


Svelte 5 Requirement

The v9 Svelte adapter only supports Svelte 5+. It is built on Svelte 5 runes such as $state, $derived.by, and $effect.pre.

If your app is still on Svelte 3 or Svelte 4, choose one of these paths:

  • Stay on @tanstack/svelte-table@8.
  • Migrate the app to Svelte 5 first, then migrate TanStack Table.

There is no Svelte 3/4 compatibility shim for the v9 Svelte adapter.


Core Breaking Changes

Entrypoint Rename

ts
// v8
import { createSvelteTable } from '@tanstack/svelte-table'

const table = createSvelteTable(options)

// v9
import { createTable } from '@tanstack/svelte-table'

const table = createTable(options)

New Required features Table Option

In Table V9, you must explicitly declare which features your table uses. Features, Row Models, and Row Model processing "Fns" are defined on the new features table option.

ts
// Table V8
import {
  createSvelteTable,
  getCoreRowModel,
  getSortedRowModel,
  sortingFns,
} from '@tanstack/svelte-table'

const table = createSvelteTable({
  columns,
  data,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  sortingFns,
})

// Table V9
import {
  createSortedRowModel,
  createTable,
  rowSortingFeature,
  sortFns,
  tableFeatures,
} from '@tanstack/svelte-table'

// All table options that concern including code modules (features, row models, Fns, etc.)
const features = tableFeatures({
  rowSortingFeature, // new - import and pass the feature you want to use
  sortedRowModel: createSortedRowModel(), // now row models are defined on the features object
  sortFns, // now Fns are defined on the features object
  // ...more features, row models, etc.
})

const table = createTable({
  features, // new required option
  columns,
  get data() {
    return data
  },
})

In Svelte 5, pass reactive values like data through getters so table options read the current rune value.

Shortcut: Use stockFeatures for Table V8-like Behavior

stockFeatures is useful for early migration when you have not audited feature usage yet.

ts
import { createTable, stockFeatures } from '@tanstack/svelte-table'

const table = createTable({
  features: stockFeatures,
  columns,
  get data() {
    return data
  },
})

Use it as a temporary migration shortcut. Explicit feature registration is the production target.

Available Features

FeatureImport Name
Column FacetingcolumnFacetingFeature
Column FilteringcolumnFilteringFeature
Column GroupingcolumnGroupingFeature
Column OrderingcolumnOrderingFeature
Column PinningcolumnPinningFeature
Column ResizingcolumnResizingFeature
Column SizingcolumnSizingFeature
Column VisibilitycolumnVisibilityFeature
Global FilteringglobalFilteringFeature
Row AggregationrowAggregationFeature
Row ExpandingrowExpandingFeature
Row PaginationrowPaginationFeature
Row PinningrowPinningFeature
Row SelectionrowSelectionFeature
Row SortingrowSortingFeature

Row Models

Row model factories now live as slots directly inside tableFeatures({...}). The rowModels option no longer exists. Row model slots are type-checked, so each row model must be specified after its associated feature in the same tableFeatures call.

Migration Mapping

Table V8 OptionTable V9 tableFeatures SlotTable V9 Factory Function
getCoreRowModel()(automatic)Not needed, always included
getFilteredRowModel()filteredRowModelcreateFilteredRowModel()
getSortedRowModel()sortedRowModelcreateSortedRowModel()
getPaginationRowModel()paginatedRowModelcreatePaginatedRowModel()
getExpandedRowModel()expandedRowModelcreateExpandedRowModel()
getGroupedRowModel()groupedRowModelcreateGroupedRowModel()
getFacetedRowModel()facetedRowModelcreateFacetedRowModel()
getFacetedMinMaxValues()facetedMinMaxValuescreateFacetedMinMaxValues()
getFacetedUniqueValues()facetedUniqueValuescreateFacetedUniqueValues()

Function registries move to slots too: pass filterFns, sortFns, and aggregationFns directly to tableFeatures instead of as factory arguments.

Full Migration Example

svelte
<script lang="ts">
  // v8
  import {
    createSvelteTable,
    getCoreRowModel,
    getFilteredRowModel,
    getPaginationRowModel,
    getSortedRowModel,
    filterFns,
    sortingFns,
  } from '@tanstack/svelte-table'

  const table = createSvelteTable({
    columns,
    data,
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    filterFns,
    sortingFns,
  })
</script>
svelte
<script lang="ts">
  // v9
  import {
    columnFilteringFeature,
    createFilteredRowModel,
    createPaginatedRowModel,
    createSortedRowModel,
    createTable,
    filterFns,
    rowPaginationFeature,
    rowSortingFeature,
    sortFns,
    tableFeatures,
  } from '@tanstack/svelte-table'

  const features = tableFeatures({
    columnFilteringFeature,
    rowPaginationFeature,
    rowSortingFeature,
    filteredRowModel: createFilteredRowModel(),
    sortedRowModel: createSortedRowModel(),
    paginatedRowModel: createPaginatedRowModel(),
    filterFns,
    sortFns,
  })

  let data = $state(makeData(1000))

  const table = createTable({
    features,
    columns,
    get data() {
      return data
    },
  })
</script>

Prefer Individual Fn Imports Over Full Registries

The filterFns, sortFns, and aggregationFns registry exports are now deprecated in favor of importing individual filterFn_*, sortFn_*, and aggregationFn_* functions and registering only the ones you use (or passing functions directly in column definitions with no registration at all). The full registries still work, but spreading them puts every built-in function in your bundle. Keep in mind that string names, including the default 'auto', only resolve functions you have registered.

ts
// Before: registers every built-in function
import { filterFns, sortFns } from '@tanstack/svelte-table'

const features = tableFeatures({
  // ...other features and row models
  filterFns,
  sortFns,
})

// After: registers only the functions you use
import {
  filterFn_includesString,
  sortFn_alphanumeric,
  sortFn_text,
} from '@tanstack/svelte-table'

const features = tableFeatures({
  // ...other features and row models
  filterFns: { includesString: filterFn_includesString },
  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
})

Instance Methods Must Be Called on Their Instance

In v9, methods on rows, cells, columns, headers, and similar table objects are shared on the object's prototype instead of being created as arrow functions on each object. This improves memory usage, but it means destructuring those methods loses the this context they need to operate on the instance.

ts
// v8 - worked because getValue closed over the row object
const { getValue } = row
const value = getValue('name')

// v9 - call the method on the instance
const value = row.getValue('name')

This applies to row, cell, column, header, and related instance APIs, but not to the table instance itself. Audit code that destructures methods from table objects or passes them around as bare callbacks. Prefer calling them through the original object, for example row.getValue('name'), cell.getContext(), column.getCanSort(), or header.getContext().

Because these methods now live on the prototype, they also do not appear as own properties in Object.keys(instance), object spread, or JSON.stringify. A shallow clone like { ...row } copies row data but does not copy row methods. The methods are still callable normally because JavaScript looks them up through the prototype chain.


State Management Changes

Svelte v9 table state is atom-backed and rune-aware. Do not port v8 writable-store table option patterns directly except as "before" code.

SurfaceUse
table.atoms.<slice>.get()Narrow, rune-aware read for one registered state slice.
table.store.get()Rune-aware full-state read; intentionally updates for any table state change.
table.baseAtoms.<slice>Internal writable atoms. Prefer feature APIs or externally owned state instead.

Accessing State

ts
// v8
const sorting = table.getState().sorting

// v9: narrow read (preferred for one slice)
const sorting = table.atoms.sorting.get()

// v9: complete state
const state = table.store.get()

The same reads become reactive dependencies when they run in a Svelte template, $derived, $derived.by, or $effect:

svelte
<script lang="ts">
  const table = createTable({
    features,
    columns,
    get data() {
      return data
    },
  })

  const pagination = $derived(table.atoms.pagination.get())
  const pageIndex = $derived(table.atoms.pagination.get().pageIndex)
  const stateJson = $derived(JSON.stringify(table.store.get(), null, 2))
</script>

<strong>Page {pagination.pageIndex + 1}</strong>

$derived now owns projection and equality behavior. A full-store read is appropriate for debug output, persistence, or computations that intentionally depend on every slice; use an atom when only one slice matters.

SvelteTable has two generic parameters, AppSvelteTable has five, and useTableContext only accepts its optional row-data generic.

Controlled State

The v8-style state plus on[State]Change pattern still works in v9 and is the most direct migration path. Prefer Svelte $state for state owned by a component. Use External Atoms when the app must share raw TanStack Store atoms outside the table.

Use createTableState for Svelte-owned state slices that need to accept TanStack Table updater functions.

svelte
<script lang="ts">
  import {
    createTable,
    createTableState,
    type PaginationState,
    type SortingState,
  } from '@tanstack/svelte-table'

  let data = $state(makeData(1000))

  const [sorting, setSorting] = createTableState<SortingState>([])
  const [pagination, setPagination] = createTableState<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,
  })
</script>

The v8-style onStateChange callback is gone. Use per-slice on[State]Change callbacks or external atoms.

If you want to lift or listen to any state change, set up a subscription to the table.store:

ts
const unsubscribe = table.store.subscribe((state) => {
  console.log(state)
})

External Atoms

Use external atoms when the app should own and share state slices outside the table.

svelte
<script lang="ts">
  import { createAtom, useSelector } from '@tanstack/svelte-store'
  import type { PaginationState, SortingState } from '@tanstack/svelte-table'

  const sortingAtom = createAtom<SortingState>([])
  const paginationAtom = createAtom<PaginationState>({
    pageIndex: 0,
    pageSize: 10,
  })

  const sorting = useSelector(sortingAtom)
  const pagination = useSelector(paginationAtom)

  const table = createTable({
    features,
    columns,
    get data() {
      return data
    },
    atoms: {
      sorting: sortingAtom,
      pagination: paginationAtom,
    },
  })
</script>

<span>Page {pagination.current.pageIndex + 1}</span>

Do not provide both atoms.pagination and state.pagination; the atom owns that slice.


Feature-by-Feature Breaking Changes

Sorting

v8v9
sortingFnsortFn
sortingFnssortFns
getSortingFn()getSortFn()
getAutoSortingFn()getAutoSortFn()
SortingFnSortFn

Column Pinning

V9 changes column pinning to use logical start/end terminology instead of the physical left/right terminology used in V8. In LTR languages/layouts, start usually corresponds to left and end to right; in RTL languages/layouts, start usually corresponds to right and end to left. There are no deprecated aliases.

V8V9
columnPinning.leftcolumnPinning.start
columnPinning.rightcolumnPinning.end
column.pin('left')column.pin('start')
column.pin('right')column.pin('end')
column.getIsPinned() === 'left'column.getIsPinned() === 'start'
column.getIsPinned() === 'right'column.getIsPinned() === 'end'
row.getLeftVisibleCells()row.getStartVisibleCells()
row.getRightVisibleCells()row.getEndVisibleCells()
table.getLeftHeaderGroups()table.getStartHeaderGroups()
table.getRightHeaderGroups()table.getEndHeaderGroups()
table.getLeftLeafColumns()table.getStartLeafColumns()
table.getRightLeafColumns()table.getEndLeafColumns()
table.getLeftVisibleLeafColumns()table.getStartVisibleLeafColumns()
table.getRightVisibleLeafColumns()table.getEndVisibleLeafColumns()
table.getLeftTotalSize()table.getStartTotalSize()
table.getRightTotalSize()table.getEndTotalSize()
column.getStart('left')column.getStart('start')
column.getAfter('right')column.getAfter('end')
column.getIndex('left')column.getIndex('start')
column.getIndex('right')column.getIndex('end')

This rename is about logical table regions, not automatic DOM direction handling. For sticky column pinning, prefer CSS logical properties like insetInlineStart and insetInlineEnd. The columnResizeDirection table option is unchanged.

Table-level enablePinning split into:

ts
enableColumnPinning: true
enableRowPinning: true

Column Sizing vs. Column Resizing Split

Column resizing now has its own feature and state slice.

ts
const features = tableFeatures({
  columnSizingFeature,
  columnResizingFeature,
})

columnSizingInfo became columnResizing, and onColumnSizingInfoChange became onColumnResizingChange.

Grouping and Aggregation

Aggregation is now its own feature, independent from column grouping. stockFeatures still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add rowAggregationFeature whenever columns use aggregationFn, aggregatedCell, getAggregationValue, or cell.getIsAggregated. Add columnGroupingFeature and groupedRowModel only when you also group rows.

ts
const features = tableFeatures({
  rowAggregationFeature,
  columnGroupingFeature, // only for grouped rows
  groupedRowModel: createGroupedRowModel(),
  aggregationFns: { sum: aggregationFn_sum },
})

Custom aggregation callables have changed to context-based definitions:

ts
// Table V8/earlier V9 betas
const total = (columnId, leafRows, childRows) =>
  leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0)

// Current V9
const total = constructAggregationFn({
  aggregate: ({ rows, getValue }) =>
    rows.reduce((sum, row) => sum + Number(getValue(row)), 0),
})

The old per-function choice between childRows and leafRows is replaced by a single depth-selected context.rows, controlled by the maxAggregationDepth column option. The default (0) preserves V8's direct-child grouped aggregation; use Infinity to aggregate terminal leaf rows.

column.getAggregationValue() now takes a single options object instead of positional arguments:

ts
// Table V8/earlier V9 betas
column.getAggregationValue(rows, maxDepth)

// Current V9
column.getAggregationValue({ rows, maxDepth })

column.getAggregationFn() is now column.getAggregationFns() because a column can run multiple definitions, and the old callable AggregationFn/CreatedAggregationFn types are replaced by AggregationFnDef.

See the Grouping Guide and the Aggregation Guide for full documentation of the new capabilities.

Row Selection

Warning

Minor breaking change: row.getToggleSelectedHandler() now enables inclusive Shift range selection by default when rowSelectionFeature is enabled. Existing checkboxes or rows wired through this handler establish an anchor on an ordinary interaction and select or deselect the current display-order range on a Shift interaction. Direct row.toggleSelected() calls are unchanged.

Set enableRowRangeSelection: false to preserve the previous non-range handler behavior. The handler must receive an event that exposes Shift directly or through nativeEvent; see Shift Range Selection.

The "some rows selected" checks were simplified to mean "at least one row is selected":

APIv8v9
table.getIsSomeRowsSelected()true when some but not all rows are selectedtrue when at least one row is selected
table.getIsSomePageRowsSelected()true when some but not all page rows are selectedtrue when at least one page row is selected

In v8 these returned false once every row was selected; in v9 they stay true. If you use them to drive an indeterminate "select all" checkbox, gate the indeterminate state on the matching all-selected check so it clears at full selection:

getIsSomeRowsSelected() && !getIsAllRowsSelected()

Row and Internal API Changes

Some row APIs have changed from private to public:

Table V8Table V9
row._getAllCellsByColumnId() (private)row.getAllCellsByColumnId() (public)

All other internal APIs prefixed with _ have been removed. If you were using any of these, use their public equivalents:

  • Removed: table._getPinnedRows()
  • Removed: table._getFacetedRowModel()
  • Removed: table._getFacetedMinMaxValues()
  • Removed: table._getFacetedUniqueValues()

Column Helper Changes

Column helpers and column types now include TFeatures first.

ts
// 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([...]) for better inference across nested columns.


Rendering Changes

Replace v8 flexRender calls with the Svelte FlexRender component.

svelte
<!-- v8 -->
<svelte:component
  this={flexRender(header.column.columnDef.header, header.getContext())}
/>

<!-- v9 -->
<FlexRender {header} />
<FlexRender {cell} />

For Svelte components in column definitions, use renderComponent.

ts
import { renderComponent } from '@tanstack/svelte-table'
import StatusCell from './StatusCell.svelte'

const columns = columnHelper.columns([
  columnHelper.accessor('status', {
    cell: ({ row }) => renderComponent(StatusCell, { row }),
  }),
])

For Svelte snippets, use renderSnippet.

svelte
<script lang="ts">
  import { renderSnippet } from '@tanstack/svelte-table'

  const columns = columnHelper.columns([
    columnHelper.accessor('firstName', {
      cell: ({ row }) => renderSnippet(nameCell, row),
    }),
  ])
</script>

{#snippet nameCell(row)}
  <strong>{row.original.firstName}</strong>
{/snippet}

The tableOptions() Utility

tableOptions() helps compose shared table option fragments.

ts
import { tableOptions } from '@tanstack/svelte-table'

const baseOptions = tableOptions({
  features,
  defaultColumn: {
    minSize: 40,
  },
})

const table = createTable({
  ...baseOptions,
  columns,
  get data() {
    return data
  },
})

createTableHook: Composable Table Patterns

createTableHook creates shared Svelte table helpers with features (including row model slots) and registered components already bound.

ts
import { createTableHook } from '@tanstack/svelte-table'

export const { createAppTable, createAppColumnHelper } = createTableHook({
  features,
})

const columnHelper = createAppColumnHelper<Person>()

const table = createAppTable({
  columns,
  get data() {
    return data
  },
})

See the Composable Tables Guide for full patterns.


TypeScript Changes Summary

Type Generics

Use TFeatures as the first generic:

ts
ColumnDef<typeof features, Person>
Column<typeof features, Person>
Row<typeof features, Person>
Table<typeof features, Person>

Using typeof features

ts
const features = tableFeatures({
  rowSortingFeature,
  rowPaginationFeature,
})

const columnHelper = createColumnHelper<typeof features, Person>()

Using StockFeatures

ts
import type { StockFeatures } from '@tanstack/svelte-table'

type PersonColumn = ColumnDef<StockFeatures, Person>

TableMeta/ColumnMeta Typing Changes

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.

ts
declare module '@tanstack/svelte-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:

ts
const features = tableFeatures({
  rowSortingFeature,
  columnMeta: metaHelper<{ align?: 'left' | 'right' }>(),
})

See the new Table and Column Meta Guide for full details on both approaches.

FilterFns/SortFns/AggregationFns/FilterMeta Augmentation Replaced by Registry Slots

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:

ts
// v8
declare module '@tanstack/svelte-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: { 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). Once a custom function is registered in a registry slot, prefer the string reference in column defs (sortFn: 'fuzzy') over passing the function directly; svelte-check is stricter about function variance, and the string form sidesteps it. See the Fuzzy Filtering Guide for a complete example.

RowData Type Restriction

Prefer explicit object row types:

ts
type Person = {
  firstName: string
  lastName: string
  age: number
}

Migration Checklist

  • Upgrade the app to Svelte 5.
  • Replace createSvelteTable with createTable.
  • Define features using tableFeatures() (or use stockFeatures)
  • Move row model factories into tableFeatures({...}) as slots (e.g. filteredRowModel: createFilteredRowModel()).
  • Remove getCoreRowModel; the core row model is automatic.
  • Pass sortFns, filterFns, and aggregationFns as slots in tableFeatures({...}) instead of as factory arguments (row model factories no longer take arguments).
  • Replace destructured row/cell/column/header methods with calls on the instance (for example, row.getValue('name')).
  • Rename sortingFn to sortFn.
  • Convert custom aggregation callables to constructAggregationFn({ aggregate, merge? }) definitions.
  • Replace Svelte 3/4 writable-store table patterns with runes and getters.
  • Pass reactive data and controlled state slices through getters.
  • Remove second-argument selectors from createTable and createAppTable.
  • Replace table.state and table.getState() reads with table.atoms.<slice>.get() or table.store.get().
  • Replace selector projections with native $derived values.
  • Remove subscribeTable / SubscribeSource imports and update SvelteTable / AppSvelteTable generic arity.
  • Replace onStateChange with per-slice callbacks or external atoms.
  • Replace declare module augmentation of FilterFns/SortFns/AggregationFns with registry-slot registration, and FilterMeta augmentation with the filterMeta slot.
  • Add typeof features to column helpers and types.
  • Replace flexRender(...) and <svelte:component> table rendering with <FlexRender />.
  • Use renderComponent or renderSnippet for Svelte component/snippet cells.
  • Audit stockFeatures before production.

Examples