Skip to content
All essays
WebMarch 27, 202513 min

Svelte Stores: State Management Guide

Master Svelte stores for reactive state management. Learn writable, readable, derived, and custom stores.

Ü
Ümit Uz
Mobile & Full Stack Developer

Svelte stores provide a reactive state management solution that works seamlessly within and outside components.

What Are Stores?

Stores are objects that hold reactive values. When the value changes, all components that subscribe to the store automatically update.

Writable Stores

Creating a Writable Store

javascript
import { writable } from 'svelte/store';

const count = writable(0);

// Subscribe to changes
const unsubscribe = count.subscribe(value => {
  console.log(value);
});

// Update value
count.set(1);
count.update(n => n + 1);

// Unsubscribe
unsubscribe();

Using Writable Stores in Components

svelte
<script>
  import { count } from './stores';

  function increment() {
    count.update(n => n + 1);
  }

  function decrement() {
    count.update(n => n - 1);
  }

  function reset() {
    count.set(0);
  }
</script>

<h1>{$count}</h1>
<button on:click={increment}>+</button>
<button on:click={decrement}>-</button>
<button on:click={reset}>Reset</button>

Writable Store with Methods

javascript
// stores/user.js
import { writable } from 'svelte/store';

function createUserStore() {
  const { subscribe, set, update } = writable(null);

  return {
    subscribe,
    login: (userData) => set(userData),
    logout: () => set(null),
    updateProfile: (updates) => {
      update(user => ({ ...user, ...updates }));
    }
  };
}

export const user = createUserStore();

Readable Stores

Readable stores cannot be updated from the outside:

javascript
import { readable, derived } from 'svelte/store';
import { browser } from '$app/environment';

// Time store
export const time = readable(new Date(), (set) => {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return () => clearInterval(interval);
});

// Location store
export const location = readable(null, (set) => {
  if (browser && navigator.geolocation) {
    const watcher = navigator.geolocation.watchPosition(
      position => {
        set({
          lat: position.coords.latitude,
          lng: position.coords.longitude
        });
      },
      error => console.error(error)
    );

    return () => navigator.geolocation.clearWatch(watcher);
  }
});

Derived Stores

Derive values from other stores:

javascript
import { derived } from 'svelte/store';

const firstName = writable('John');
const lastName = writable('Doe');

// Simple derived store
export const fullName = derived(
  [firstName, lastName],
  ([$first, $last]) => `${$first} ${$last}`
);

// Derived with custom logic
export const greeting = derived(
  fullName,
  ($fullName, set) => {
    set(`Hello, ${$fullName}!`);

    // Cleanup
    return () => console.log('Greeting updated');
  },
  'Hello!' // Initial value
);

// Derived from single store
export const doubled = derived(
  count,
  $count => $count * 2
);

Custom Stores

Creating a Reusable Store Pattern

javascript
// stores/createStore.js
import { writable, derived } from 'svelte/store';

export function createStore(initial) {
  const store = writable(initial);

  const { subscribe, set, update } = store;

  return {
    subscribe,
    update,
    set,
    reset: () => set(initial),
    patch: (updates) => {
      update(current => ({ ...current, ...updates }));
    }
  };
}

// Usage
export const user = createStore({ name: '', email: '' });

Todo Store Example

javascript
// stores/todos.js
import { writable, derived } from 'svelte/store';

function createTodos() {
  const { subscribe, update } = writable([]);

  return {
    subscribe,

    add: (text) => {
      update(todos => [
        ...todos,
        { id: Date.now(), text, completed: false }
      ]);
    },

    toggle: (id) => {
      update(todos =>
        todos.map(todo =>
          todo.id === id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      );
    },

    remove: (id) => {
      update(todos => todos.filter(todo => todo.id !== id));
    },

    clear: () => {
      update(() => []);
    }
  };
}

export const todos = createTodos();

// Derived stores
export const completedCount = derived(
  todos,
  $todos => $todos.filter(t => t.completed).length
);

export const activeCount = derived(
  todos,
  $todos => $todos.filter(t => !t.completed).length
);

Store Persistence

LocalStorage Store

javascript
// stores/persisted.js
import { writable } from 'svelte/store';

function persisted(key, initial) {
  const stored = localStorage.getItem(key);

  const store = writable(stored ? JSON.parse(stored) : initial);

  store.subscribe($value => {
    localStorage.setItem(key, JSON.stringify($value));
  });

  return store;
}

export const user = persisted('user', null);
export const theme = persisted('theme', 'light');

Store Composition

Combining Multiple Stores

javascript
// stores/cart.js
import { derived } from 'svelte/store';
import { products } from './products';
import { cartItems } from './cartItems';

export const cartTotal = derived(
  [products, cartItems],
  ([$products, $cartItems]) => {
    return $cartItems.reduce((total, item) => {
      const product = $products.find(p => p.id === item.productId);
      return total + (product?.price || 0) * item.quantity;
    }, 0);
  }
);

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

Async Stores

Fetching Data

javascript
// stores/posts.js
import { writable } from 'svelte/store';

function createAsyncStore(fetcher) {
  const store = writable({
    data: null,
    loading: true,
    error: null
  });

  async function load() {
    store.update(s => ({ ...s, loading: true, error: null }));

    try {
      const data = await fetcher();
      store.set({ data, loading: false, error: null });
    } catch (error) {
      store.update(s => ({
        ...s,
        loading: false,
        error: error.message
      }));
    }
  }

  return {
    subscribe: store.subscribe,
    load
  };
}

export const posts = createAsyncStore(async () => {
  const res = await fetch('/api/posts');
  return res.json();
});

Testing Stores

javascript
// tests/stores/counter.spec.js
import { tick } from 'svelte';
import { get } from 'svelte/store';
import { count } from '../stores';

describe('Counter Store', () => {
  it('should increment', () => {
    count.set(0);
    count.update(n => n + 1);
    expect(get(count)).toBe(1);
  });

  it('should decrement', () => {
    count.set(5);
    count.update(n => n - 1);
    expect(get(count)).toBe(4);
  });
});

Best Practices

  1. 1Keep stores focused - One store per domain
  2. 2Use derived stores for computed values
  3. 3Persist critical state - LocalStorage for important data
  4. 4Avoid direct mutations - Use update methods
  5. 5Clean up subscriptions - Always unsubscribe

Svelte stores provide a simple yet powerful way to manage state in your applications.

Related essays

Next essay
Svelte Transitions and Animations