Skip to content

The Store Contract

There is nothing magic about a Svelte store. A store is any object with a subscribe method that takes a callback and calls it with the current value, immediately and on every change — returning an unsubscribe function. That’s the whole contract:

// The minimal store contract, written by hand.
function createCounter() {
let value = 0;
const subscribers = new Set();
return {
subscribe(fn) {
fn(value); // 1. call immediately with the current value
subscribers.add(fn);
return () => subscribers.delete(fn); // 2. return an unsubscribe function
},
increment() {
value += 1;
subscribers.forEach((fn) => fn(value)); // 3. notify on change
},
};
}

Because the contract is this small, anything that implements it is a store — including RxJS observables (which also have a compatible subscribe). That interoperability is the reason Svelte defined a contract instead of a concrete class.

Writing store.subscribe(...) by hand in every component would be tedious and leak-prone. Inside a component, Svelte gives you the $store shorthand: prefix a store with $ and Svelte subscribes for you, gives you the current value, re-renders when it changes, and unsubscribes automatically when the component is destroyed.

<script>
import { counter } from './counter.js';
</script>
<!-- $counter is the current value; the component re-renders on every change -->
<button onclick={counter.increment}>count is {$counter}</button>
flowchart LR
  prefix["$store in a component"] --> sub["subscribe on mount"]
  sub --> val["read the current value"]
  val --> rerender["re-render on change"]
  rerender --> unsub["auto-unsubscribe on destroy"]
What the $ prefix does for you

You can also assign to $store ($counter = 5) if the store is writable — Svelte compiles that to calling the store’s set. The $ prefix only works inside .svelte files and .svelte.js/.svelte.ts modules, because it’s compiler magic, not a runtime function.

Why the contract still matters in Svelte 5

Section titled “Why the contract still matters in Svelte 5”

Even though runes now cover most shared-state needs (next lessons), the store contract remains valuable:

  • Interop. Any observable-like source with a compatible subscribe works with $ — RxJS, custom event streams, third-party libraries.
  • Async sources. readable (next lesson) wraps a push-based source (a WebSocket, a geolocation watcher) behind the same simple contract.
  • Existing code. Millions of lines of Svelte use stores; understanding the contract lets you read and maintain them.
What makes something a Svelte store?
What does the `$store` prefix do inside a component?
Why can an RxJS observable work with Svelte's `$` syntax?
Where does the `$store` prefix work?