name: svelte/migrate-v8-to-v9
description: >
Mechanical migration from @tanstack/svelte-table@8 to @tanstack/svelte-table@9. v9 in Svelte
is a full rewrite — Svelte 5 runes only (no Svelte 3/4), no /legacy adapter (unlike React),
createSvelteTable → createTable, getCoreRowModel / getSortedRowModel factories →
row-model factories registered as slots inside tableFeatures({...}), flexRender helper →
<FlexRender> component, writable-store state → rune-based getters / external atoms,
onStateChange → per-slice on[State]Change or atoms. Plan a feature-by-feature audit, not a
search-and-replace.
type: lifecycle
library: tanstack-table
framework: svelte
library_version: '9.0.0-alpha.48'
requires:
- setup
- state-management
- column-definitions
sources:
- TanStack/table:docs/framework/svelte/svelte-table.md
- TanStack/table:docs/framework/svelte/guide/table-state.md
- TanStack/table:packages/svelte-table/src/
- TanStack/table:examples/svelte/basic-create-table/
- TanStack/table:examples/svelte/basic-external-atoms/
- TanStack/table:examples/svelte/basic-external-state/
Migrate v8 → v9 (Svelte)
CRITICAL: v9 = Svelte 5
v9 of the Svelte adapter only supports Svelte 5+. No backport. No shim. No /legacy import.
The v9 adapter is built on Svelte 5 runes ($state, $derived.by, $effect.pre). If your app
is still on Svelte 3/4, you have two real options:
- Stay on
@tanstack/svelte-table@8. v8 keeps working with the Svelte 3/4 writable-store API. - Migrate the app to Svelte 5 first, then migrate the table.
There is no third option. Trying to install v9 on a Svelte 4 codebase will error at compile.
This makes the Svelte migration heavier than React's. React has a
/legacyre-export that mirrors the v8 surface; Svelte does not. Plan for a real rewrite of every table screen.
What changed at the type / API level
| v8 | v9 |
|---|---|
createSvelteTable(options) |
createTable(options, selector?) |
getCoreRowModel() |
included by default; no factory |
getPaginationRowModel() |
tableFeatures({ paginatedRowModel: createPaginatedRowModel() }) |
getSortedRowModel() |
tableFeatures({ sortedRowModel: createSortedRowModel(), sortFns }) |
getFilteredRowModel() |
tableFeatures({ filteredRowModel: createFilteredRowModel(), filterFns }) |
getExpandedRowModel() |
tableFeatures({ expandedRowModel: createExpandedRowModel() }) |
getGroupedRowModel() |
tableFeatures({ groupedRowModel: createGroupedRowModel(), aggregationFns }) |
getFacetedRowModel() / MinMax / UniqueValues |
facet APIs auto-derived when *Facet*Feature registered |
flexRender(template, ctx) helper |
<FlexRender header={h} /> / <FlexRender cell={c} /> / <FlexRender footer={h} /> |
ColumnDef<TData> |
ColumnDef<TFeatures, TData> (extra generic) |
createColumnHelper<TData>() |
createColumnHelper<TFeatures, TData>() |
| writable store on the table instance | table.atoms.<slice> + table.store + table.state |
onStateChange (monolithic) |
per-slice on[State]Change, or external atoms |
useSvelteTable (rare) |
gone |
What did NOT change
- The data array is still the source of truth; columns are still defined the same way
shape-wise (
accessorKey/accessorFn/header/cell/footer). - Filter, sort, aggregation function registries are still
filterFns,sortFns,aggregationFns— but now passed into row-model factories instead of being auto-resolved. - Feature APIs (
table.nextPage(),column.getCanSort(),row.toggleSelected()) keep the same names.
Migration checklist (per file)
For each Svelte component that uses the table:
- Upgrade Svelte. Ensure the app is on Svelte 5 and the component compiles in runes mode.
- Replace store with rune.
let data = writable([])→let data = $state([]). Reads inside markup are no longer$data— justdata. - Import surface.
import { createSvelteTable, getCoreRowModel, flexRender }→import { createTable, FlexRender, tableFeatures, ... }. - Add
features. Createconst features = tableFeatures({ ... only the features this table uses ... }). - Move row-model factories into
tableFeatures. Everyget*RowModel: get*RowModel()becomes a slot insidetableFeatures({ ..., create*RowModel() }). Any*Fnsregistry that was passed as a factory argument is now also a named slot in the sametableFeaturescall. - Generic columns. Add
<typeof features, TData>toColumnDef<>andcreateColumnHelper<>(). TypeScript will tell you when you missed one. dataas a getter.data: data→get data() { return data }. Same for any other reactive option (state.*,columns,rowCount).- State. Pick the new ownership model — see below.
- Rendering.
{flexRender(header.column.columnDef.header, header.getContext())}→<FlexRender header={header} />. Same for cells and footers. - Components / snippets in cells. v8: pass a Svelte component constructor. v9: wrap with
renderComponent(MyCell, props)orrenderSnippet(snippet, args).
State ownership — choose one per slice
Was: writable store + onStateChange
<script lang="ts">
// v8
import { writable } from 'svelte/store'
const options = writable({ ... })
$: $options.state = $state
</script>
Now: pick one of three
Internal (default). Pass only initialState and let the table own it.
const table = createTable({
features,
columns,
get data() {
return data
},
initialState: {
pagination: { pageIndex: 0, pageSize: 25 },
},
})
External state + per-slice on[State]Change. Closest to a literal v8 port.
<script lang="ts">
let sorting: SortingState = $state([])
let pagination: PaginationState = $state({ pageIndex: 0, pageSize: 10 })
const table = createTable({
features,
columns,
get data() {
return data
},
state: {
get sorting() {
return sorting
},
get pagination() {
return pagination
},
},
onSortingChange: (updater) => {
sorting = updater instanceof Function ? updater(sorting) : updater
},
onPaginationChange: (updater) => {
pagination = updater instanceof Function ? updater(pagination) : updater
},
})
</script>
External atoms (preferred for shared state). Atomic, subscribable from anywhere.
import { createAtom, useSelector } from '@tanstack/svelte-store'
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,
},
})
Do not combine state.pagination with atoms.pagination — atoms always win, state is
discarded silently, you'll think your callbacks aren't firing.
Rendering migration cheat sheet
<!-- v8 -->
<th>
{#if !header.isPlaceholder}
<svelte:component
this={flexRender(header.column.columnDef.header, header.getContext())}
/>
{/if}
</th>
<!-- v9 -->
<th>
{#if !header.isPlaceholder}
<FlexRender {header} />
{/if}
</th>
For cells that render a custom Svelte component:
<!-- v8: column def -->
{ cell: () => MyCellComponent }
<!-- v9: column def -->
import { renderComponent } from '@tanstack/svelte-table'
{ cell: ({ row }) => renderComponent(MyCellComponent, { row }) }
For inline snippets:
<!-- v9 -->
import { renderSnippet } from '@tanstack/svelte-table'
{#snippet myCell(row)}
<strong>{row.original.firstName}</strong>
{/snippet}
{ cell: ({ row }) => renderSnippet(myCell, row) }
After the rewrite — verify
pnpm test:types(orsvelte-check) catches missing<typeof features, TData>generics, missingfeaturesor row-model factory slots, and feature APIs called on tables that didn't register them.pnpm buildshould pass with the newfeaturesset. Bundle should shrink — only the features you register are included.- Click through every table screen. Reset buttons, multi-sort, pagination reset on filter,
expanded-row count under pagination — these are all the spots where v8 muscle memory
reaches for an option (
autoResetPageIndexetc.) that's still named the same in v9 but only takes effect if the matching feature is registered.
Common failure modes during migration
- Trying to skip the Svelte 5 upgrade. Will not work.
- Reaching for
useLegacyTable. Doesn't exist in@tanstack/svelte-table. That's a React-only escape hatch. - Importing from
@tanstack/table-coredirectly. Re-exported by the adapter — go through the adapter. - Plain
datainstead ofget data()getter. Largest single source of "but my data changed" bugs during migration. - Plain
features: { rowPaginationFeature }instead oftableFeatures({...}). Loses inference; you'll getanyeverywhere. - Forgetting feature registration after adding the row-model factory slot. Adding
paginatedRowModel: createPaginatedRowModel()totableFeatureswithout also includingrowPaginationFeaturedoes nothing. - Reimplementing v8 helpers (
flexRender-style functions, manual store subscriptions, hand-rolled selectors) instead of usingFlexRender/subscribeTable/ atom selectors. That's the #1 AI tell on this migration.
Related skills
tanstack-table/svelte/getting-started— clean-slate setup.tanstack-table/svelte/table-state— the v9 state model in depth.tanstack-table/core/state-management— atom precedence rules.tanstack-table/svelte/production-readiness— post-migration tuning.