The Chrome Extension plugin ships two building blocks —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/christianbaroni/stores/llms.txt
Use this file to discover all available pages before exploring further.
ChromeStorageAdapter and ChromeExtensionSyncEngine — plus the createSyncedChromeStorage convenience factory that wires them together. Together they let you persist store state to any chrome.storage area and propagate changes automatically across every extension context: background service worker, popup, options page, and content scripts. Because Chrome itself fires chrome.storage.onChanged events whenever storage is written, no additional message-passing infrastructure is needed.
Installation
Import directly from the@storesjs/stores/chrome sub-path:
API Reference
ChromeStorageAdapter
ChromeStorageAdapter implements AsyncStorageInterface and maps store persistence operations onto a chrome.storage area. Every read and write returns a Promise, so the adapter works correctly in both Manifest V2 and Manifest V3 service workers.
| Option | Type | Default | Description |
|---|---|---|---|
area | AreaName | 'local' | The chrome.storage area to read from and write to. |
storageKeyPrefix | string | 'stores:' | Namespaces every key to avoid collisions with other extensions or libraries. |
async = true, so @storesjs/stores automatically awaits all storage operations before reporting hydration as complete.
ChromeExtensionSyncEngine
ChromeExtensionSyncEngine implements SyncEngine and uses chrome.storage.onChanged as its transport layer. When any context writes to chrome.storage, Chrome dispatches an onChanged event in every other open context. The engine listens for those events and forwards the changed fields into the store’s sync pipeline automatically — no WebSocket, BroadcastChannel, or custom messaging needed.
sessionId on construction and uses it to filter out self-originated storage changes, preventing stores from re-applying their own writes.
publish is null because broadcasting is handled implicitly by chrome.storage writes — the engine never needs to push updates out explicitly.
ChromeExtensionSyncEngine sets injectStorageMetadata = true internally. This causes the persist middleware to embed an origin session ID, timestamp, and changed-field list into each persisted value. Without this metadata the engine cannot distinguish remote updates from local ones, and cross-context sync will not function correctly. You do not need to set injectStorageMetadata yourself when using this engine.createSyncedChromeStorage(options?)
A convenience factory that creates a matched ChromeStorageAdapter and ChromeExtensionSyncEngine pair, with the engine automatically configured to share the adapter’s area and storageKeyPrefix.
Setup Walkthrough
Create the storage and sync engine
Call
createSyncedChromeStorage once per extension, ideally in a shared module that every context imports:Configure global defaults (optional)
Pass the adapter and sync engine to
configureStores to use them as the defaults for every store in the extension without repeating them per-store:Create a store with per-store storage and sync
If you prefer explicit per-store configuration — or need different stores to target different areas — pass
storage and sync directly to createBaseStore:Use the store in any extension context
Because sync is driven by Any context that changes
chrome.storage.onChanged, the same store definition works identically in your popup, options page, background service worker, or content script. Import the store and use it like any other Stores hook:theme will have the update reflected in every other open context automatically.Real-World Example
The following pattern is taken directly from themissionControlStore in the Chrome extension example. It uses createSyncedChromeStorage to share a single storage/engine pair across a complex store with many synced fields:
Sync Behavior
The sync transport is entirely passive: theChromeExtensionSyncEngine attaches a single chrome.storage.onChanged listener when the first store registers. When any context writes to storage, Chrome delivers the change event — including the previous and new values — to all other contexts that share the same extension origin.
The engine processes each change event by:
- Checking the origin. If
syncMetadata.originmatches the current session’s ID, the event was self-originated and is discarded. - Extracting changed fields. Field-level metadata embedded in the persisted value identifies exactly which fields changed. When metadata is absent (e.g., data written by an older version), the engine falls back to a full state diff against the previous value.
- Applying the update. Changed fields are forwarded to the store’s sync pipeline, which handles conflict resolution and subscriber notification.
syncEngine.destroy() is called explicitly.
Testing
A set of mock Chrome storage utilities (mockChromeStorage.testUtils.ts) is available in the source for unit testing stores that use ChromeStorageAdapter. The mock mirrors real Chromium semantics: it performs deep equality checks before firing change events, dispatches changes asynchronously via microtasks, and produces immutable snapshot payloads — matching the documented behavior of chrome.storage.local and chrome.storage.session.
The mock utilities are test-only helpers. They are not exported from the public
@storesjs/stores/chrome bundle and should only be imported in test environments (e.g., via a vitest or jest setup file).