Custom Driver
Implement a driver when an existing storage backend is not available in the built-in collection.
A driver is an object that works with normalized, mount-relative keys. Only hasItem, getItem, and getKeys are required; write methods are optional so drivers can be read-only.
#Minimal driver
This example implements a small in-memory driver:
import type { Driver } from "unstorage";
type MyDriverOptions = {
initial?: Record<string, string>;
};
export function myDriver(
options: MyDriverOptions = {},
): Driver<MyDriverOptions, Map<string, string>> {
const data = new Map(Object.entries(options.initial || {}));
return {
name: "my-driver",
options,
getInstance: () => data,
hasItem(key) {
return data.has(key);
},
getItem(key) {
return data.get(key) ?? null;
},
getKeys(base) {
return [...data.keys()].filter((key) => !base || key.startsWith(base));
},
setItem(key, value) {
data.set(key, value);
},
removeItem(key) {
data.delete(key);
},
clear(base) {
for (const key of data.keys()) {
if (!base || key.startsWith(base)) {
data.delete(key);
}
}
},
dispose() {
data.clear();
},
};
}Use it like any built-in driver:
import { createStorage } from "unstorage";
import { myDriver } from "./my-driver";
const storage = createStorage({
driver: myDriver(),
});
await storage.setItem("user:1", { name: "Ada" });Note
The regular storage API serializes values before passing them to a driver's setItem, then deserializes values returned by getItem. A persistent driver should therefore store and return strings for the regular API. Implement getItemRaw and setItemRaw when the backend also supports native or binary values.
#Driver contract
A driver can expose the following properties and methods:
| Member | Purpose |
|---|---|
name | Stable name used in diagnostics and introspection. |
options | Original driver options. Avoid putting secrets here when tracing may be enabled. |
flags | Advertises native support such as { maxDepth: true, ttl: true }. |
getInstance() | Returns the underlying client or storage object. |
hasItem(key, options) | Checks whether a key exists. Required. |
getItem(key, options) | Returns a serialized value or null. Required. |
getKeys(base, options) | Returns keys relative to the driver mount. Required. |
setItem, removeItem, clear | Enable write operations. Omit them for a read-only driver. |
getItems, setItems | Optional experimental batch implementations. |
getItemRaw, setItemRaw | Optional experimental native/binary operations. |
getMeta | Returns backend metadata such as mtime, size, or ttl. |
watch(callback) | Reports backend changes and returns a cleanup function. |
dispose() | Closes clients, watchers, timers, and other resources. |
Methods may return values directly or return promises.
#Key and mount rules
- Keys use the normalized
foo:barformat. The storage instance normalizes/and\\separators before calling the driver. - Drivers receive keys relative to their mount. A driver mounted at
cachereceivesuser:1for the storage keycache:user:1. getKeys(base)must return keys relative to the driver, not absolute storage keys.- Treat
baseas a key prefix and avoid returning unrelated siblings such asfoobarfor the basefoo.
Explore the built-in driver implementations for production examples.
#Watching
When a driver omits watch, unstorage emits events for writes made through the storage instance. When a driver implements watch, it becomes responsible for reporting changes:
watch(callback) {
const stop = backend.onChange((key, removed) => {
callback(removed ? "remove" : "update", key);
});
return () => stop();
}Emit "update" after values change and "remove" after deletion. The callback key must be relative to the driver mount. Clean up all listeners in both the returned function and dispose().
#Optional dependencies
Drivers that integrate a third-party package should load it lazily so importing the driver does not force every user to install that package. Built-in drivers expose a DRIVER_DEPENDENCIES export and accept a lib option for this purpose; see Driver dependencies for the public convention.