Stores
Svelte stores and state management patterns including writable, readable, derived, and custom stores
You are an expert in Svelte stores and state management, helping developers manage shared state across components using built-in store primitives, custom stores, and modern Svelte 5 alternatives.
## Key Points
- **Use custom stores to encapsulate business logic.** Expose semantic methods (`addItem`, `toggle`) instead of raw `set`/`update`.
- **Prefer `derived` for computed values** rather than subscribing to one store and setting another.
- **Avoid store subscriptions in server-side code.** Stores are singletons, which means shared state across requests on the server. Use SvelteKit's `locals` or load function data instead.
- **Use runes for new Svelte 5 projects** when you do not need the store contract for interop. They offer a simpler API and deep reactivity.
- **Clean up manual subscriptions.** If you call `store.subscribe()` directly (outside a component's `$` shorthand), you must call the returned unsubscribe function to prevent memory leaks.
- **Forgetting the `$` prefix.** Referencing `count` instead of `$count` in a template gives you the store object, not its value.
- **Over-subscribing.** Each `$store` reference in a component creates a subscription. For expensive derived computations, extract the value once into a local variable.
- **Mutating store values in place.** `$store.push(item)` does not trigger an update. You must reassign: `$store = [...$store, item]` or use `store.update()`.
- **Using stores where props suffice.** If state is only needed by a parent and its direct children, pass it as props rather than creating a global store.
## Quick Example
```js
// src/lib/stores/counter.js
import { writable } from 'svelte/store';
export const count = writable(0);
```
```js
export const theme = persisted('theme', 'light');
```skilldb get svelte-skills/StoresFull skill: 249 linesStores and State Management — Svelte
You are an expert in Svelte stores and state management, helping developers manage shared state across components using built-in store primitives, custom stores, and modern Svelte 5 alternatives.
Core Philosophy
Svelte stores solve the problem of sharing reactive state across components that are not in a direct parent-child relationship. The store contract is deliberately minimal: any object with a subscribe method that returns an unsubscribe function qualifies as a store. This simplicity means you can wrap virtually any external data source — WebSockets, event emitters, browser APIs — into something that works with Svelte's $ auto-subscription syntax.
The built-in store primitives (writable, readable, derived) form a composable toolkit. A writable store holds mutable state. A readable store exposes values that consumers cannot directly set, with a setup function that controls the value. Derived stores compute values from other stores, creating a dependency graph that updates automatically. The best store designs combine these primitives into custom stores that expose semantic methods (addItem, toggle, clear) rather than raw set/update, encapsulating business logic behind a clean interface.
In Svelte 5, runes in .svelte.js modules offer an alternative to stores for shared state. The choice between them is practical: use runes when you want deep reactivity and simpler syntax for new code; use stores when you need the store contract for interop with SvelteKit's built-in stores ($page, $navigating) or third-party libraries. Both approaches are valid, and most applications will use a mix.
Anti-Patterns
-
Module-Level Stores in SSR — declaring a writable store at the module level without considering that it is a singleton on the server. Two simultaneous SSR requests share the same store, causing data to leak between users. Use context or load function data for request-scoped state.
-
In-Place Mutation Without Reassignment — writing
$store.push(item)expecting it to trigger a reactivity update. Svelte stores require reassignment ($store = [...$store, item]) orstore.update()to notify subscribers of changes. -
Using Stores for Parent-Child Communication — creating a global store to pass data between a parent and its direct children when simple props would be clearer, more explicit, and require no subscription management.
-
Forgetting Manual Subscription Cleanup — calling
store.subscribe()directly (outside a component's$shorthand) without storing and calling the returned unsubscribe function, causing memory leaks that accumulate over time. -
Over-Subscribing in Templates — referencing
$storemultiple times in a template when a single local variable assignment would suffice. Each$storereference creates a subscription, and for expensive derived stores, this multiplies unnecessary computation.
Overview
Svelte stores are reactive containers for values that can be shared across components. The svelte/store module provides writable, readable, and derived store constructors. In Svelte 5, runes ($state in .svelte.js modules) offer an alternative for shared state, but stores remain essential for working with SvelteKit's built-in stores ($page, $navigating) and for library interoperability.
Core Concepts
Writable Stores
A writable store holds a value that can be read and updated from anywhere.
// src/lib/stores/counter.js
import { writable } from 'svelte/store';
export const count = writable(0);
<script>
import { count } from '$lib/stores/counter.js';
</script>
<!-- Auto-subscription with $ prefix -->
<p>Count: {$count}</p>
<button onclick={() => count.update(n => n + 1)}>Increment</button>
<button onclick={() => count.set(0)}>Reset</button>
The $ prefix auto-subscribes and unsubscribes when the component is destroyed.
Readable Stores
A readable store exposes a value that consumers cannot directly set.
import { readable } from 'svelte/store';
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => set(new Date()), 1000);
return () => clearInterval(interval); // cleanup
});
Derived Stores
Derive a new store from one or more existing stores.
import { derived } from 'svelte/store';
import { items } from './items.js';
export const totalPrice = derived(items, ($items) =>
$items.reduce((sum, item) => sum + item.price * item.qty, 0)
);
// From multiple stores
import { exchangeRate } from './settings.js';
export const totalInEUR = derived(
[totalPrice, exchangeRate],
([$total, $rate]) => $total * $rate
);
Store Contract
Any object with a subscribe method returning an unsubscribe function is a valid Svelte store and works with the $ prefix. This means you can wrap external state sources:
export function fromEventSource(url) {
return {
subscribe(callback) {
const source = new EventSource(url);
source.onmessage = (e) => callback(JSON.parse(e.data));
return () => source.close();
}
};
}
Implementation Patterns
Custom Stores with Encapsulated Logic
Wrap a writable store and expose only specific operations:
// src/lib/stores/cart.js
import { writable, derived } from 'svelte/store';
function createCart() {
const { subscribe, set, update } = writable([]);
return {
subscribe,
addItem(item) {
update(items => {
const existing = items.find(i => i.id === item.id);
if (existing) {
return items.map(i =>
i.id === item.id ? { ...i, qty: i.qty + 1 } : i
);
}
return [...items, { ...item, qty: 1 }];
});
},
removeItem(id) {
update(items => items.filter(i => i.id !== id));
},
clear() {
set([]);
}
};
}
export const cart = createCart();
export const cartTotal = derived(cart, ($cart) =>
$cart.reduce((sum, item) => sum + item.price * item.qty, 0)
);
Persisted Store
Sync a writable store with localStorage:
// src/lib/stores/persisted.js
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export function persisted(key, initialValue) {
const stored = browser ? localStorage.getItem(key) : null;
const data = stored ? JSON.parse(stored) : initialValue;
const store = writable(data);
if (browser) {
store.subscribe((value) => {
localStorage.setItem(key, JSON.stringify(value));
});
}
return store;
}
export const theme = persisted('theme', 'light');
SvelteKit Built-in Stores
SvelteKit provides several stores via $app/stores:
<script>
import { page, navigating } from '$app/stores';
</script>
<!-- Current URL, params, data, errors -->
<p>Path: {$page.url.pathname}</p>
<p>User: {$page.data.user?.name}</p>
<!-- Navigation state -->
{#if $navigating}
<div class="loading-bar" />
{/if}
Svelte 5 Runes as Store Alternative
For new Svelte 5 projects, shared reactive state can be managed with runes in .svelte.js modules:
// src/lib/state/cart.svelte.js
let items = $state([]);
export function addItem(item) {
const existing = items.find(i => i.id === item.id);
if (existing) {
existing.qty++;
} else {
items.push({ ...item, qty: 1 });
}
}
export function removeItem(id) {
const index = items.findIndex(i => i.id === id);
if (index !== -1) items.splice(index, 1);
}
export const total = $derived(
items.reduce((sum, i) => sum + i.price * i.qty, 0)
);
export function getItems() {
return items;
}
This approach gives you deep reactivity and simpler syntax but does not implement the store contract (no $ prefix auto-subscription in templates — you call functions or reference exported state directly).
Best Practices
- Use custom stores to encapsulate business logic. Expose semantic methods (
addItem,toggle) instead of rawset/update. - Prefer
derivedfor computed values rather than subscribing to one store and setting another. - Avoid store subscriptions in server-side code. Stores are singletons, which means shared state across requests on the server. Use SvelteKit's
localsor load function data instead. - Use runes for new Svelte 5 projects when you do not need the store contract for interop. They offer a simpler API and deep reactivity.
- Clean up manual subscriptions. If you call
store.subscribe()directly (outside a component's$shorthand), you must call the returned unsubscribe function to prevent memory leaks.
Common Pitfalls
- Shared state across SSR requests. A module-level writable store is a singleton on the server. Two simultaneous requests will share and overwrite the same store value. Use context or load function data for request-scoped state.
- Forgetting the
$prefix. Referencingcountinstead of$countin a template gives you the store object, not its value. - Over-subscribing. Each
$storereference in a component creates a subscription. For expensive derived computations, extract the value once into a local variable. - Mutating store values in place.
$store.push(item)does not trigger an update. You must reassign:$store = [...$store, item]or usestore.update(). - Using stores where props suffice. If state is only needed by a parent and its direct children, pass it as props rather than creating a global store.
Install this skill directly: skilldb add svelte-skills
Related Skills
Component Patterns
Svelte component composition patterns including props, snippets, context, and advanced reuse techniques
Form Actions
SvelteKit form actions for progressive enhancement with server-side form handling
Load Functions
SvelteKit server and universal load functions for fetching and passing data to pages and layouts
Reactivity
Svelte 5 runes system for fine-grained reactivity with $state, $derived, and $effect
Sveltekit Auth
Authentication patterns in SvelteKit using hooks, cookies, sessions, and OAuth flows
Sveltekit Routing
SvelteKit file-based routing system including layouts, groups, and advanced route patterns