Plugins
Plugins are opt-in. Attach them to a manager with createStateManager().use(...), then opt a store into the matching feature through its definition options.
Each plugin provides exactly one capability and does not affect stores that do not configure it. Use this page to choose the right one for the job. Use the reference pages when you need exact signatures, controller shapes, or type names.
Choose the right plugin
- Keep state after reloads, sessions, or restarts
- Use
createPersistencePlugin() - Read next: Persistence helpers
- Use
- Support undo, redo, and time travel
- Use
createHistoryPlugin() - Read next: Plugins and orchestration
- Use
- Model explicit workflow states
- Use
createFsmPlugin() - Read next: Finite state machines
- Use
- Keep multiple tabs in sync
- Use
createSyncPlugin() - Read next: Plugins and orchestration
- Use
- Track loading, errors, cancellation, and concurrency
- Use
createAsyncPlugin() - Read next: Examples and recipes
- Use
- Reject invalid state patches
- Use
createValidationPlugin() - Read next: Validation
- Use
- Inspect stores during development
- Use
statelyVitePlugin()and inspector helpers - Read next: Inspector
- Use
Persistence
Use persistence when the store owns durable settings, drafts, or session state that should survive reloads.
Stately supports:
- memory,
localStorage,sessionStorage, and IndexedDB-shaped adapters - optional
lz-stringcompression - custom
serialize()anddeserialize()hooks pickandomitfor selective persistencemigrate()for schema upgradesttlto discard stale persisted state on rehydration
import {
createLocalStorageAdapter,
createLzStringCompression,
createPersistencePlugin,
createStateManager,
defineStore
} from '@selfagency/stately';
const manager = createStateManager().use(createPersistencePlugin());
export const useSessionStore = defineStore('session', {
state: () => ({ theme: 'dark', token: '' }),
persist: {
adapter: createLocalStorageAdapter(),
version: 1,
omit: ['token'],
compression: createLzStringCompression()
}
});Use Persistence helpers for the full option surface.
Custom persistence hooks receive the concrete store state type, so interface state stays strongly typed through serialize() and deserialize().
History and time travel
Use history when users benefit from undo and redo, or when you need replayable debugging.
The history plugin adds $history and $timeTravel, including:
undo()andredo()goTo(index)for replaying a specific snapshotrecord(snapshot)for manual entriesstartBatch()andendBatch()for grouping several mutations into one logical history entry
Time travel intentionally suppresses persistence and sync side effects during replay so you do not create feedback loops or overwrite current state with historical snapshots.
Finite state machines
Use the FSM plugin when the store has named workflow states and legal transitions between them.
It is a better fit than ad hoc booleans when your UI needs states like idle, loading, success, error, editing, or submitted.
The plugin adds $fsm with:
currentsend(event, ...args)matches(...states)can(event)
Read Finite state machines for the full workflow pattern.
Sync across tabs and environments
Use sync when multiple browser contexts should converge on the same store state.
The sync plugin:
- uses BroadcastChannel when available and storage events as a fallback transport stack
- rejects mismatched versions
- ignores self-originated messages
- applies a timestamp-first ordering policy with deterministic origin and
mutationIdtie-breakers - only patches known state keys
If you use createMessage to add transport metadata, remember that the callback is manager-wide. Treat the incoming base payload as a generic sync message and perform your own narrowing if you need store-specific fields.
Use Plugins and orchestration for the detailed conflict rules.
Async orchestration
Use the async plugin when actions can overlap or be cancelled.
It adds a $async registry and supports concurrency policies such as:
parallelrestartabledropenqueuededupe
If you need cancellation, configure injectSignal so the wrapped action receives an AbortSignal in the position your action expects.
Validation
Use validation when state changes must satisfy invariants immediately after a patch completes.
The validation plugin can:
- accept a patch by returning
trueorundefined - reject a patch by returning an error string
- roll the store back to the previous snapshot on failure
- call
onValidationErrorbefore throwing
Read Validation for the usage pattern and Validation reference for the exact behavior.
Plugin authoring
Most consumers can stop here.
If you are authoring your own plugin, use the public types in Public types. In particular:
- augment
DefineStoreOptionsBasefor new store-definition options - augment
StoreCustomPropertiesfor new store instance properties - prefer
defineStateManagerPlugin()when you want compile-time checking for the augmentation shape the plugin returns
All built-in plugins use typed return signatures with explicit augmentation generics, so you can use them as reference implementations for your own plugin authoring.