# State persistence

> serializeState and restoreState, what is persisted, dropping columns that no longer exist, storage as the application choice.

Page: https://svelte-lean.vercel.app/table/state-persistence · Markdown: https://svelte-lean.vercel.app/table/state-persistence.md · Index: https://svelte-lean.vercel.app/llms.txt

TableState is a plain object, so persisting a view is serializing it. The state entry point adds a versioned envelope and a restore that drops what no longer fits the schema. Where it is stored is the application's choice.

- **Source**: state.tscore.ts (defaultState)
- **Tests**: core.test.tsdata-table.dom.test.tstable.test.tstable.spec.ts (browser)
- **Fixture**: fixtures/table-basic
- **Benchmark**: table.bench.spec.ts

Presentation state is worth persisting; data is not. The state describes how the user looks at the rows: sort rules, filters, the page, hidden columns, their order, widths and pins. Restoring it later must not break when the schema changed in the meantime, so `restoreState()` takes the current columns and discards entries that refer to a column that no longer exists, an operator it does not know, or a value of the wrong type. A malformed or foreign string yields the default state instead of an exception.

The helpers are pure and measure 796 B gzip as an entry; they do not touch storage. Selection and expansion ids are kept as they are, because only the application knows whether a row id is still valid.

`what is persisted`

```ts
// serializeState(state) → a versioned envelope
{ "version": 1, "state": { "sorting": [], "filters": [], "search": "", "pageIndex": 0, "pageSize": 20,
  "selection": [], "expanded": [], "hidden": [], "order": [], "widths": {}, "pinning": {},
  "rowPinning": { "top": [], "bottom": [] } } }

// restoreState(text, columns) → a complete TableState
// - unknown version or malformed JSON → defaultState()
// - hidden, order, sorting, filters, widths, pinning: entries for unknown column ids are dropped
// - filters with an unknown operator are dropped; widths must be positive numbers
// - selection and expanded: only string and finite number ids are kept
```

`a URL as storage`

```ts
// The storage is yours: localStorage, a URL, a server-side preference.
const params = new URLSearchParams(location.search);
params.set('view', serializeState(state));
history.replaceState(null, '', `?${params}`);
```

## Serialize and restore

Serialize presentation state and safely restore only columns that still exist.

PersistedState.svelte

```svelte
<script lang="ts">
	import { DataTable, defaultState, type TableState } from '@svelte-lean/table';
	import { restoreState, serializeState } from '@svelte-lean/table/state';
	import { memberColumns } from '$lib/demo/columns';
	import { people } from '$lib/demo/data';

	const KEY = 'members-layout';
	const data = people(12);
	const columns = memberColumns();
	let tableState = $state<Partial<TableState>>(defaultState({ pageSize: 6 }));
	let notice = $state('');

	// Storage is the application's choice: localStorage here, a URL or a server elsewhere.
	function save() {
		localStorage.setItem(KEY, serializeState(defaultState(tableState)));
		notice = 'Layout saved in localStorage.';
	}
	function restore(withColumns = columns) {
		const saved = localStorage.getItem(KEY);
		if (!saved) return (notice = 'Nothing saved yet.');
		tableState = restoreState(saved, withColumns); // unknown columns are dropped
		notice = 'Layout restored.';
	}
</script>

<button type="button" data-slean="button" data-variant="outlined" data-color="neutral" onclick={save}>Save</button>
<button type="button" data-slean="button" data-variant="outlined" data-color="neutral" onclick={() => restore()}>Restore</button>
<button type="button" data-slean="button" data-variant="outlined" data-color="neutral" onclick={() => restore(columns.filter((c) => c.id !== 'region'))}>
	Restore without the Region column
</button>
<button type="button" data-slean="button" data-variant="outlined" data-color="neutral" onclick={() => (tableState = defaultState({ pageSize: 6 }))}>Reset</button>

<DataTable {data} {columns} getRowId={(row) => row.id} bind:state={tableState} columnControls searchable label="Members" />

<pre>{serializeState(defaultState(tableState))}</pre>
<p role="status">{notice}</p>
```

Sort, hide, reorder or resize columns, search, change the page, then Save. Reset and Restore bring the view back from localStorage. Restore without the Region column restores against a schema that lacks that column: its width, order, pin, hidden and sort entries are dropped, the rest survives. The pre element prints the serialized text.

- The envelope carries a version; a future format change bumps it and old strings restore to defaults.
- `defaultState(partial)` fills a partial state before serializing so every field is present.

## API

| Name | Of | Type | Default | Description |
| --- | --- | --- | --- | --- |
| [`sorting`](https://svelte-lean.vercel.app/table/api.md#state-sorting) | TableState | `Sort[]` | `[]` | Ordered sort rules; the array order is the multi-sort precedence. |
| [`filters`](https://svelte-lean.vercel.app/table/api.md#state-filters) | TableState | `Filter[]` | `[]` | Column filters with operator and value. |
| [`search`](https://svelte-lean.vercel.app/table/api.md#state-search) | TableState | `string` | `''` | The search text; terms are split on whitespace. |
| [`pageIndex`](https://svelte-lean.vercel.app/table/api.md#state-pageindex) | TableState | `number` | `0` | Zero-based page. |
| [`pageSize`](https://svelte-lean.vercel.app/table/api.md#state-pagesize) | TableState | `number` | `20` | Rows per page; 0 shows every row. |
| [`hidden`](https://svelte-lean.vercel.app/table/api.md#state-hidden) | TableState | `string[]` | `[]` | Hidden column ids. |
| [`order`](https://svelte-lean.vercel.app/table/api.md#state-order) | TableState | `string[]` | `[]` | Explicit leaf column order. |
| [`widths`](https://svelte-lean.vercel.app/table/api.md#state-widths) | TableState | `Record<string, number>` | `{}` | Resized widths by column id. |
| [`pinning`](https://svelte-lean.vercel.app/table/api.md#state-pinning) | TableState | `Record<string, Pin>` | `{}` | Pin overrides by column id. |
| [`defaultState`](https://svelte-lean.vercel.app/table/api.md#export-defaultstate) | Root entry | `(patch?: Partial<TableState>) => TableState` | – | A complete state object with optional overrides. |
| [`@svelte-lean/table/state`](https://svelte-lean.vercel.app/table/api.md#module--svelte-lean-table-state) | Entry points | `serializeState · restoreState` | – | Versioned persistence of presentation state; unknown columns are dropped on restore. |

## Related

- [Columns](https://svelte-lean.vercel.app/table/columns.md)
- [Server data](https://svelte-lean.vercel.app/table/server-data.md)
- [Table state](https://svelte-lean.vercel.app/table/api.md#state)
