4 min read
On this page

Stores

Stores are Svelte's built-in solution for shared reactive state. A store is any object with a subscribe method that follows the Svelte store contract. The framework provides writable, readable, and derived stores out of the box, plus the $ auto-subscription syntax that eliminates boilerplate. Stores are the right tool when components that are not in a parent-child relationship need to share state.

Writable Stores

A writable store holds a value that can be read, set, and updated from anywhere:

// src/lib/stores/counter.ts
import { writable } from 'svelte/store';

export const count = writable(0);
<!-- ComponentA.svelte -->
<script>
  import { count } from '$lib/stores/counter';
</script>

<p>Count: {$count}</p>
<button onclick={() => $count++}>Increment</button>
<!-- ComponentB.svelte (anywhere in the app) -->
<script>
  import { count } from '$lib/stores/counter';
</script>

<p>The count is also {$count} here.</p>
<button onclick={() => $count = 0}>Reset</button>

Both components see the same value. When one updates it, the other updates automatically.

The $ Auto-Subscription

The $ prefix before a store name is compiler sugar. Writing $count in a component is equivalent to subscribing to the store and reading its current value. Assignments to $count call count.set(). The subscription is cleaned up automatically when the component is destroyed.

Without the $ syntax, you would write:

<script>
  import { count } from '$lib/stores/counter';
  import { onDestroy } from 'svelte';

  let value;
  const unsubscribe = count.subscribe(v => { value = v; });
  onDestroy(unsubscribe);
</script>

<p>{value}</p>

The $ prefix saves you from this ceremony. Use it.

set & update

Stores have set and update methods:

import { writable } from 'svelte/store';

const count = writable(0);

count.set(10);           // Set to a specific value
count.update(n => n + 1); // Update based on current value

Inside a component, $count = 10 calls set, and $count++ calls update. In non-component code (.ts files), use the methods directly.

Readable Stores

A readable store exposes a value that consumers cannot modify directly. The value is controlled by the store creator:

// src/lib/stores/time.ts
import { readable } from 'svelte/store';

export const now = readable(new Date(), (set) => {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return () => clearInterval(interval);
});
<script>
  import { now } from '$lib/stores/time';
</script>

<p>Current time: {$now.toLocaleTimeString()}</p>

The second argument to readable is a start function that runs when the first subscriber appears. It receives set to update the value. The returned function is the cleanup, called when the last subscriber unsubscribes. This makes readable stores ideal for:

  • Clocks and timers.
  • Geolocation tracking.
  • Media query watchers.
  • Any data source where consumers should only observe, not modify.

Derived Stores

A derived store computes its value from one or more other stores:

// src/lib/stores/cart.ts
import { writable, derived } from 'svelte/store';

export const items = writable([
  { name: 'Widget', price: 9.99, quantity: 2 },
  { name: 'Gadget', price: 24.99, quantity: 1 }
]);

export const totalItems = derived(items, ($items) =>
  $items.reduce((sum, item) => sum + item.quantity, 0)
);

export const totalPrice = derived(items, ($items) =>
  $items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
<script>
  import { items, totalItems, totalPrice } from '$lib/stores/cart';
</script>

<p>{$totalItems} items, total: ${$totalPrice.toFixed(2)}</p>

Derived stores from multiple sources:

import { derived } from 'svelte/store';

export const summary = derived(
  [items, discountCode],
  ([$items, $discountCode]) => {
    const subtotal = $items.reduce((s, i) => s + i.price * i.quantity, 0);
    const discount = $discountCode === 'SAVE10' ? 0.1 : 0;
    return {
      subtotal,
      discount: subtotal * discount,
      total: subtotal * (1 - discount)
    };
  }
);

Custom Stores

A custom store wraps a writable store with a domain-specific API. Any object with a subscribe method is a valid store:

// src/lib/stores/auth.ts
import { writable } from 'svelte/store';

interface User {
  id: string;
  name: string;
  email: string;
}

function createAuthStore() {
  const { subscribe, set, update } = writable<User | null>(null);

  return {
    subscribe,
    login: async (email: string, password: string) => {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
        headers: { 'Content-Type': 'application/json' }
      });
      if (!response.ok) throw new Error('Login failed');
      const user = await response.json();
      set(user);
      return user;
    },
    logout: async () => {
      await fetch('/api/auth/logout', { method: 'POST' });
      set(null);
    },
    updateProfile: (changes: Partial<User>) => {
      update(user => user ? { ...user, ...changes } : null);
    }
  };
}

export const auth = createAuthStore();
<script>
  import { auth } from '$lib/stores/auth';
</script>

{#if $auth}
  <p>Welcome, {$auth.name}</p>
  <button onclick={() => auth.logout()}>Logout</button>
{:else}
  <button onclick={() => auth.login('user@example.com', 'password')}>Login</button>
{/if}

The $auth syntax works because the store exposes subscribe. The login and logout methods provide a clean API without exposing raw set and update.

When Stores Are the Right Tool

Stores solve one specific problem: sharing reactive state between components that are not in a parent-child relationship.

Use stores when:

  • A notification system needs to be updated from an API utility and displayed in a header component.
  • A shopping cart state is needed by the header (item count), the cart page, and the checkout flow.
  • User authentication state is needed across many unrelated components.

Do not use stores when:

  • State only flows from parent to child. Use props.
  • State is shared within a component tree. Use context.
  • State is local to one component. Use $state.
Decision flow:
  Is the state local to one component?  -> $state
  Is it passed from parent to child?    -> props
  Is it shared within a component tree? -> context
  Is it truly global?                   -> store

Stores & Svelte 5

In Svelte 5, $state and runes handle most local and component-level reactivity. Stores remain useful for global shared state, but their role is narrower. You can also use .svelte.ts files with $state for shared reactive state as an alternative to stores:

// src/lib/counter.svelte.ts (alternative to a store)
let count = $state(0);

export function getCount() { return count; }
export function increment() { count++; }
export function reset() { count = 0; }

The choice between stores and .svelte.ts modules depends on your preference. Stores have the $ auto-subscription syntax. Reactive modules use getters or function calls. Both work.

Common Pitfalls

  • Overusing stores: Most state does not need to be global. Start with $state and props. Promote to context or stores only when you actually need cross-component sharing.
  • Forgetting to unsubscribe in non-component code: The $ syntax auto-unsubscribes in components. In plain .ts files or onMount callbacks, you must manually unsubscribe: const unsub = store.subscribe(...); onDestroy(unsub);.
  • Mutating store values directly: $items.push(newItem) does not trigger an update in Svelte 4 stores because it mutates the array without calling set. Use $items = [...$items, newItem] or items.update(arr => [...arr, newItem]). (In Svelte 5 with $state, mutations are tracked, but stores use the older model.)
  • Creating stores inside components: A store defined inside a component is recreated on each mount, defeating the purpose of shared state. Define stores in separate module files.
  • Circular derived stores: Deriving store A from store B and store B from store A creates an infinite loop. Design your store graph as a DAG (directed acyclic graph).

Key Takeaways

  • Writable stores hold shared mutable state. Readable stores expose values that consumers cannot modify. Derived stores compute values from other stores.
  • The $ prefix auto-subscribes to stores in components and handles cleanup automatically.
  • Custom stores wrap a writable with a domain-specific API, exposing only the operations that make sense.
  • Stores are for global shared state between unrelated components. Do not use them for state that props, context, or $state can handle.
  • In Svelte 5, .svelte.ts modules with $state are an alternative to stores for shared reactive state. Choose based on your team's preference.