Unstorage
Guide for setting up and using the unstorage npm package — a universal key-value storage API with 20+ built-in drivers. Use this skill when the user is working with unstorage, importing from "unstorage" or "unstorage/drivers/*", asking about storage drivers (memory, filesystem, Redis, Cloudflare KV, MongoDB, etc.), key-value persistence, multi-storage mounting, prefixStorage, snapshots, or building a custom driver. Trigger on mentions of "unstorage", "createStorage", "storage driver", or "universal key-value".From its SKILL.md
npx -y skills add BetaHuhn/skills --skill unstorageAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 4 commands, including `npm install unstorage` and 3 more.
- fetches URLsInstructs the agent to fetch 8 URLs, including https://unstorage.unjs.io/llms.txt and 7 more.
SKILL.md
12.9 KB, ~3.1k tokens by cl100k_base, as published. Nobody here has run it
unstorage
unstorage is a runtime-agnostic universal key-value storage API with a unified interface and 20+ built-in drivers. It works on Node.js, Bun, Deno, and edge Workers.
Install:
npm install unstorage
Core concepts
- All keys use
foo:barcolon-separated convention (equivalent tofoo/barpath-style) - The default driver is in-memory (
Map-based) — no config needed to get started - All methods are async and return Promises
- Values are automatically JSON-serialized/deserialized
- Drivers are mounted Unix-style at arbitrary namespace prefixes
Basic usage
import { createStorage } from "unstorage";
const storage = createStorage(/* opts */);
await storage.setItem("foo:bar", { hello: "world" });
await storage.getItem("foo:bar"); // => { hello: "world" }
await storage.hasItem("foo:bar"); // => true
await storage.removeItem("foo:bar");
Fetching up-to-date documentation
unstorage provides LLM-optimized docs. Fetch these when you need precise details on a specific driver or topic:
- Full docs index:
https://unstorage.unjs.io/llms.txt - Complete docs (single file):
https://unstorage.unjs.io/llms-full.txt
Individual pages (Markdown):
https://unstorage.unjs.io/raw/guide.md— Getting startedhttps://unstorage.unjs.io/raw/guide/utils.md— Utilitieshttps://unstorage.unjs.io/raw/guide/http-server.md— HTTP serverhttps://unstorage.unjs.io/raw/guide/custom-driver.md— Custom driverhttps://unstorage.unjs.io/raw/drivers.md— All drivers overviewhttps://unstorage.unjs.io/raw/drivers/{driver-name}.md— Specific driver (e.g.redis,fs,cloudflare,mongodb)
Always fetch the relevant driver's raw Markdown before writing driver-specific code — driver options, peer dependencies, and examples can vary significantly.
Full API reference
Read
await storage.hasItem("key"); // boolean — also: storage.has()
await storage.getItem("key"); // value | null — also: storage.get()
await storage.getItems([ // batch read (experimental)
"key1",
{ key: "key2", options: {} },
]); // => [{ key, value }, ...]
await storage.getItemRaw("file.bin"); // raw Buffer/Uint8Array
await storage.getKeys("base?"); // string[] of all keys — also: storage.keys()
await storage.getMeta("key"); // { mtime, atime, size, ...custom }
Write
await storage.setItem("key", value); // also: storage.set()
await storage.setItems([{ key, value }]); // batch write (experimental)
await storage.setItemRaw("file.bin", buffer); // raw binary write
await storage.setMeta("key", { flag: 1 }); // custom metadata (stored at key$)
Delete
await storage.removeItem("key"); // also: storage.del(), storage.remove()
await storage.removeItem("key", { removeMeta: true }); // also remove metadata
await storage.removeMeta("key");
await storage.clear("base?"); // remove all (optionally under a base)
Lifecycle
await storage.dispose(); // close open handles (call before process exit)
Watch
const unwatch = await storage.watch((event, key) => {
// event: "update" | "remove"
});
await unwatch(); // stop this specific watcher
await storage.unwatch(); // stop all watchers
TypeScript generics
// Type the return value of getItem
await storage.getItem<string>("k"); // => string | null
// Type an entire storage instance
const storage = createStorage<string>();
storage.setItem("k", "val"); // OK
storage.setItem("k", 123); // TS error
// Type a storage namespace (key map)
type StorageDefinition = {
items: { foo: string; count: number };
};
const storage = createStorage<StorageDefinition>();
Drivers
Pass a driver to createStorage({ driver }) to change the backend. The default is memory.
Memory (default)
import { createStorage } from "unstorage";
import memoryDriver from "unstorage/drivers/memory";
const storage = createStorage({ driver: memoryDriver() });
Filesystem (Node.js)
Requires no extra install. Maps keys to files on disk. Supports watch via chokidar.
import { createStorage } from "unstorage";
import fsDriver from "unstorage/drivers/fs";
const storage = createStorage({
driver: fsDriver({ base: "./data" }),
});
// Options: base, ignore, watchOptions
Lite variant (no extra dependencies):
import fsLiteDriver from "unstorage/drivers/fs-lite";
const storage = createStorage({ driver: fsLiteDriver({ base: "./data" }) });
Redis
npm install ioredis
import { createStorage } from "unstorage";
import redisDriver from "unstorage/drivers/redis";
const storage = createStorage({
driver: redisDriver({
base: "app", // key namespace prefix
host: "localhost",
port: 6379,
// password, tls, url, ttl, scanCount, preConnect, cluster, clusterOptions
}),
});
// Per-item TTL
await storage.setItem("session:abc", data, { ttl: 3600 }); // seconds
Cloudflare KV
import cloudflareKVBindingDriver from "unstorage/drivers/cloudflare-kv-binding";
// Inside a Worker:
const storage = createStorage({
driver: cloudflareKVBindingDriver({ binding: "MY_KV" }),
});
MongoDB
npm install mongodb
import mongodbDriver from "unstorage/drivers/mongodb";
const storage = createStorage({
driver: mongodbDriver({
connectionString: "mongodb://localhost:27017",
databaseName: "mydb",
collectionName: "storage",
}),
});
Browser (localStorage / sessionStorage / IndexedDB)
import localStorageDriver from "unstorage/drivers/localstorage";
import sessionStorageDriver from "unstorage/drivers/session-storage";
import indexedDbDriver from "unstorage/drivers/indexedb";
const storage = createStorage({ driver: localStorageDriver({ base: "app:" }) });
Other built-in drivers
| Driver | Import | Notes |
|---|---|---|
| LRU Cache | unstorage/drivers/lru-cache | In-memory, requires lru-cache |
| GitHub | unstorage/drivers/github | Read-only, maps repo files |
| HTTP | unstorage/drivers/http | Remote HTTP endpoint |
| Vercel KV | unstorage/drivers/vercel-kv | Requires @vercel/kv |
| Netlify Blobs | unstorage/drivers/netlify-blobs | |
| Azure | unstorage/drivers/azure-* | Multiple Azure storage services |
| SQL Database | unstorage/drivers/db | Requires db0 |
| PlanetScale | unstorage/drivers/planetscale |
Full list: https://unstorage.unjs.io/drivers
Multi-storage mounting
Mount additional drivers at specific key namespaces. Keys routed by longest matching prefix.
import { createStorage } from "unstorage";
import fsDriver from "unstorage/drivers/fs";
import redisDriver from "unstorage/drivers/redis";
const storage = createStorage({}); // default: memory
// Filesystem for assets
storage.mount("assets", fsDriver({ base: "./public" }));
// Redis for sessions
storage.mount("sessions", redisDriver({ host: "localhost" }));
// => storage.setItem("assets:logo.png", ...) → writes to ./public/logo.png
// => storage.setItem("sessions:abc", ...) → writes to Redis
// => storage.setItem("config:foo", ...) → writes to memory
Mount options:
storage.mount("logs", fsDriver({ base: "./logs" }), {
readOnly: true, // disable setItem / setItemRaw
noClear: true, // disable clear()
});
Inspect mounts:
storage.getMount("sessions:abc");
// => { base: "sessions:", driver: ... }
storage.getMounts("sessions");
// => [{ base: "sessions:", driver }]
await storage.unmount("sessions"); // unregister + dispose
Namespaced sub-storage
Create a scoped instance that prefixes all operations — useful for module isolation:
import { createStorage, prefixStorage } from "unstorage";
const storage = createStorage();
const userStorage = prefixStorage(storage, "users");
await userStorage.setItem("alice", { role: "admin" });
// Same as: storage.setItem("users:alice", { role: "admin" })
await userStorage.getKeys();
// Returns keys WITHOUT the "users:" prefix
Snapshots
Serialize all items to/from a plain object — useful for testing, seeding, or backups:
import { snapshot, restoreSnapshot } from "unstorage";
// Capture
const data = await snapshot(storage, "config"); // base is optional
// => { "theme": "dark", "lang": "en" }
// Restore
await restoreSnapshot(storage, data, "config");
Custom driver
Implement a driver using defineDriver. Only implement the methods you need.
import { createStorage, defineDriver } from "unstorage";
const myDriver = defineDriver((options: { prefix?: string } = {}) => {
const map = new Map<string, string>();
return {
name: "my-driver",
options,
async hasItem(key) { return map.has(key); },
async getItem(key) { return map.get(key) ?? null; },
async setItem(key, value) { map.set(key, value); },
async removeItem(key) { map.delete(key); },
async getKeys(base) {
return [...map.keys()].filter(k => !base || k.startsWith(base));
},
async clear(base) {
for (const key of await this.getKeys(base)) map.delete(key);
},
async dispose() { map.clear(); },
// async watch(callback) { ... } // optional
};
});
const storage = createStorage({ driver: myDriver({ prefix: "app" }) });
Custom driver rules:
- Keys follow
foo:barcolon convention getItemreturns a string, serializable object, ornull- If you implement
watch, unstorage's default change events are disabled — you must emit them manually ongetItem,setItem, andremoveItem - Clean up open handles (timers, connections) in
dispose()
HTTP server
Expose a storage instance over HTTP for remote access:
npm install listhen
// server.ts
import { listen } from "listhen";
import { createStorage } from "unstorage";
import { createStorageServer } from "unstorage/server";
const storage = createStorage();
const storageServer = createStorageServer(storage, {
authorize(req) {
// req: { key, type, event }
if (req.type === "read" && req.key.startsWith("private:")) {
throw new Error("Unauthorized");
}
},
});
await listen(storageServer.handle);
HTTP method mapping:
GET /key→getItem(key)GET /base/→getKeys(base)HEAD /key→hasItem(key)(returns 404 if missing)PUT /key→setItem(key, body)DELETE /key→removeItem(key)DELETE /base/→clear(base)
Connect a client using the HTTP driver:
import httpDriver from "unstorage/drivers/http";
const client = createStorage({
driver: httpDriver({ base: "http://localhost:3000" }),
});
Common patterns
Caching with TTL (Redis)
const cache = createStorage({
driver: redisDriver({ host: "localhost", ttl: 60 }),
});
await cache.setItem("result:1", data, { ttl: 300 }); // 5 min TTL
Feature flag / config store
const config = prefixStorage(storage, "config");
await config.setItem("feature:dark-mode", true);
const enabled = await config.getItem<boolean>("feature:dark-mode");
Layered storage (overlay pattern)
Use overlayDriver to stack a writeable layer on top of a read-only base:
import overlayDriver from "unstorage/drivers/overlay";
import fsDriver from "unstorage/drivers/fs";
import memoryDriver from "unstorage/drivers/memory";
const storage = createStorage({
driver: overlayDriver({
layers: [memoryDriver(), fsDriver({ base: "./defaults" })],
}),
});
Common pitfalls
- Missing
await— all storage methods are async; forgettingawaitsilently does nothing - Key format inconsistency — prefer
foo:baroverfoo/bar; both work but mixing causes confusion - Not calling
dispose()— drivers with open handles (Redis, DB connections, FS watchers) will leak if not disposed before process exit - Mutating objects from
getItem— the returned value may be a reference; clone it if needed - No TTL support in all drivers — TTL is only supported where the underlying store supports it (e.g. Redis); memory driver ignores
ttl watchin custom drivers — if you implementwatch, you must emit events yourself; unstorage's default handler is disabled
Further reading
- Drivers reference: https://unstorage.unjs.io/drivers
- Custom driver guide: https://unstorage.unjs.io/guide/custom-driver
- HTTP server guide: https://unstorage.unjs.io/guide/http-server
- Utilities: https://unstorage.unjs.io/guide/utils
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.