Svelte 47: Global and Module-Level State Patterns
easy⏱ 5 mincoursesvelte
Module scope is already a singleton
This is plain JavaScript, not a Svelte feature: ES modules are evaluated once and cached by the module loader. Header.svelte and Sidebar.svelte below share one notifications store despite never referencing each other directly — they just both happen to import the same file.
// notifications.js — evaluated once, cached by the module loader
export const notifications = createNotificationCenter();
// Header.svelte and Sidebar.svelte BOTH do:
import { notifications } from './notifications.js';
// ...and get the exact same object.
Module-level $state exists too (with a caveat)
Modern Svelte 5 lets you write let count = $state(0); at the top level of a module and export functions that read/mutate it — but only inside a file named with the special .svelte.js (or .svelte.ts) extension, which tells the compiler to apply rune macros to that plain module too. It's a nice shorthand for very simple singleton state. This sandbox's virtual file system compiles .svelte files but treats plain .js files as ordinary JavaScript, so the runnable demo below sticks to the store pattern, which is guaranteed to work in every Svelte 5 setup, sandbox or not.
// counter.svelte.js — note the special extension
// (illustrative only — not executed in this sandbox)
let count = $state(0);
export function getCount() { return count; }
export function increment() { count++; }
Singleton vs per-instance — pick deliberately
A singleton store (this lesson) is right for truly app-wide state: the logged-in user, a notification center, a theme. Per-instance state — plain $state, or a store created inside a component and handed out via context (lesson 45) — is right the moment you might have TWO of something on screen that shouldn't share state.