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:

my-driver.ts
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:

MemberPurpose
nameStable name used in diagnostics and introspection.
optionsOriginal driver options. Avoid putting secrets here when tracing may be enabled.
flagsAdvertises 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, clearEnable write operations. Omit them for a read-only driver.
getItems, setItemsOptional experimental batch implementations.
getItemRaw, setItemRawOptional experimental native/binary operations.
getMetaReturns 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:bar format. The storage instance normalizes / and \\ separators before calling the driver.
  • Drivers receive keys relative to their mount. A driver mounted at cache receives user:1 for the storage key cache:user:1.
  • getKeys(base) must return keys relative to the driver, not absolute storage keys.
  • Treat base as a key prefix and avoid returning unrelated siblings such as foobar for the base foo.

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.