mv3-storage
v1.0.0
Published
Chrome extension storage wrapper for Manifest V3 (MV3) — type-safe chrome.storage with defaults, subscriptions, and atomic updates. Zero dependencies, fully typed.
Maintainers
Readme
mv3-storage
Chrome extension storage wrapper for Manifest V3 (MV3) — a type-safe wrapper over
chrome.storagewith defaults, atomic updates, and typed subscriptions. Works withlocal,sync, andsessionareas. Zero dependencies.
interface MyStore {
count: number;
user: { id: string; name: string } | null;
prefs: { theme: "light" | "dark"; lang: string };
}
import { createStorage } from "mv3-storage";
const store = createStorage<MyStore>({
area: "local",
defaults: {
count: 0,
user: null,
prefs: { theme: "light", lang: "en" },
},
});
const count = await store.get("count"); // typed: number
await store.set("user", { id: "1", name: "Ada" }); // typed: User | null
await store.update("count", (n) => n + 1); // typed: number → number
const all = await store.getAll(); // typed: MyStore
const off = store.subscribe("user", (next, prev) => {
console.log("user changed", prev, "→", next);
});Why
chrome.storage.local.get returns Promise<unknown>. Reading anything
back means casting or hand-rolling type assertions on every call site.
This package wraps the API with a single typed schema, automatic defaults,
atomic update(key, fn), and a properly-typed subscription API.
Install
pnpm add mv3-storage
# or: npm i mv3-storage
# or: yarn add mv3-storageUsage
Declare your schema
// src/shared/store.ts
import { createStorage } from "mv3-storage";
export interface AppStore {
authToken: string | null;
recentSearches: string[];
preferences: { theme: "light" | "dark"; notifications: boolean };
}
export const store = createStorage<AppStore>({
area: "sync", // or "local" or "session"
defaults: {
authToken: null,
recentSearches: [],
preferences: { theme: "light", notifications: true },
},
});Use it anywhere
import { store } from "@/shared/store";
const token = await store.get("authToken"); // string | null
await store.set("authToken", "xyz");
await store.update("recentSearches", (arr) => [...arr, "react", "vite"].slice(-10));
// React to changes
const off = store.subscribe("preferences", (next) => {
document.body.dataset.theme = next.theme;
});API
createStorage<S>(options) → Storage<S>
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| area | "local" \| "sync" \| "session" | "local" | Which chrome.storage area to use. |
| defaults | S | required | Returned when a key has no stored value. Defines the schema's keys. |
Storage<S>
| Method | Returns | Notes |
| --- | --- | --- |
| get(key) | Promise<S[K]> | Returns the default when no value is stored. |
| set(key, value) | Promise<void> | |
| update(key, fn) | Promise<S[K]> | fn receives the current value and returns (or resolves to) the next. |
| remove(key) | Promise<void> | Subsequent get returns the default. |
| getAll() | Promise<S> | All keys merged with defaults. |
| clear() | Promise<void> | Clears the entire area — including keys not in your schema. Use carefully. |
| subscribe(key, fn) | () => void | Notified on every change to key in the chosen area. |
| subscribeAll(fn) | () => void | Notified once per change batch with a Partial<S> of the new values. |
Storage area choice
| Area | Persistence | Quota | Notes |
| --- | --- | --- | --- |
| local | Until uninstall | ~10 MB | Per-device. The usual choice. |
| sync | Synced via Google account | ~100 KB | Smaller quota; synced across devices. |
| session | Cleared on browser restart | ~10 MB | In-memory; useful for ephemeral state. MV3-only. |
Caveat: update is not lock-protected
store.update(key, fn) reads, calls fn, then writes. If two callers
race on the same key, the second wins. For most extension use cases this
is fine (popup-only edits, single-SW writes); for genuinely concurrent
writes, do the merge in your handler.
Demo extension
A runnable demo lives in example/. The popup edits a typed
counter and preference settings; changes persist across popup re-opens
and surface in the SW console via subscribe.
cd example
pnpm install
pnpm build
# then load example/dist/ via chrome://extensions → "Load unpacked"See example/README.md for full instructions.
Related packages
Part of a small MV3 toolkit for Chrome / Edge / Firefox extensions by @graybearo:
mv3-message-router— typed messaging between popup, content, and service workermv3-keepalive— service-worker keepalive + durable alarmsmv3-content-bridge— content-script ↔ page-context typed bridgechrome-extension-vite-react— Vite + React + TS MV3 starterchrome-extension-webpack-react— webpack + React + TS MV3 starterawesome-mv3— curated list of MV3 tools, libraries, and resources
License
MIT — see LICENSE.
