# unstorage > Unified key-value storage API with conventional features and 20+ built-in drivers. --- # Getting Started > Use one key-value API across memory, filesystems, databases, cloud services, and browser storage. Unstorage separates your application from its storage backend. Start with the built-in memory driver, then switch drivers or mount several drivers on the same storage instance without changing the rest of your code. ## Installation :pm-install{name="unstorage"} ## Quick start ```ts [storage.ts] import { createStorage } from "unstorage"; const storage = createStorage(); await storage.setItem("user:1", { name: "Ada" }); const user = await storage.getItem("user:1"); // { name: "Ada" } ``` The default driver stores data in memory. To persist data, pass a [driver](/drivers): ```ts import { createStorage } from "unstorage"; import fsDriver from "unstorage/drivers/fs"; const storage = createStorage({ driver: fsDriver({ base: "./data" }), }); ``` ::note Keys are normalized to colon-separated paths. For example, `users/1`, `/users/1`, and `users:1` address the same key: `users:1`. :: ## Core API Data operations return promises even when the underlying driver is synchronous. Mount registration and inspection methods are synchronous. ### Reading values #### `hasItem(key, options?)` Checks whether a key exists. ```ts if (await storage.hasItem("user:1")) { // ... } ``` Alias: `storage.has()`. #### `getItem(key, options?)` Returns the deserialized value, or `null` when the key does not exist. ```ts const value = await storage.getItem("user:1"); ``` Alias: `storage.get()`. #### `getItems(items, commonOptions?)` ::warning The batch APIs are experimental and may change in a future release. :: Reads several keys. Each entry can be a key or an object with per-item options. Unstorage groups operations by mounted driver and uses the driver's batch implementation when available. ```ts const items = await storage.getItems([ "user:1", { key: "user:2", options: { consistency: "strong" } }, ]); // [{ key: "user:1", value: ... }, { key: "user:2", value: ... }] ``` ### Writing values #### `setItem(key, value, options?)` Serializes and stores a JSON-compatible value. Passing `undefined` removes the key. ```ts await storage.setItem("settings:theme", "dark"); await storage.setItem("settings:layout", { compact: true }); ``` Alias: `storage.set()`. #### `setItems(items, commonOptions?)` Stores several values. Per-item options are merged with the common options. ```ts await storage.setItems([ { key: "feature:a", value: true }, { key: "feature:b", value: false, options: { ttl: 60 } }, ]); ``` This experimental method resolves when all writes complete. #### `removeItem(key, options?)` Removes a key. Set `removeMeta` to also remove custom metadata stored for that key. ```ts await storage.removeItem("user:1", { removeMeta: true }); ``` Aliases: `storage.remove()` and `storage.del()`. ### Raw values ::warning `getItemRaw` and `setItemRaw` are experimental. Driver support and returned types vary. Follow [unjs/unstorage#142](https://github.com/unjs/unstorage/issues/142) for details. :: Use the raw API for binary data or a driver's native value format: ```ts await storage.setItemRaw("images:logo.png", new Uint8Array([1, 2, 3])); const bytes = await storage.getItemRaw("images:logo.png"); ``` When a driver does not implement raw operations, unstorage uses a serialized fallback. ## Listing and clearing keys ### `getKeys(base?, options?)` Returns full, normalized keys. A base limits the result to that prefix. Custom metadata keys are excluded. ```ts await storage.getKeys(); // ["settings:theme", "users:1", "users:2"] await storage.getKeys("users"); // ["users:1", "users:2"] await storage.getKeys("users", { maxDepth: 1 }); ``` Alias: `storage.keys()`. ### `clear(base?, options?)` Without a base, clears every writable mount. With a base, clears mounted drivers whose mount points match that base or a descendant. ```ts await storage.clear(); // clear all mounts await storage.clear("cache"); // clear mounts at or below "cache" ``` ::caution `clear(base)` targets matching mounts; it does not remove an arbitrary key prefix from the default or a parent driver. To remove such a prefix, list `getKeys(base)` and call `removeItem` for each key. :: ## Metadata Unstorage merges two metadata sources: - native driver metadata, such as filesystem modification time and size; - custom metadata stored by `setMeta`. Custom metadata overrides fields with the same name from the driver. ```ts await storage.setMeta("report", { author: "Ada", ttl: 3600 }); const meta = await storage.getMeta("report"); await storage.removeMeta("report"); ``` Pass `{ nativeOnly: true }` to `getMeta` to skip custom metadata: ```ts const nativeMeta = await storage.getMeta("report", { nativeOnly: true }); ``` Metadata support varies by driver. Transaction options such as `ttl` are also driver-specific; check the relevant [driver page](/drivers). ## Mounting multiple drivers Mounts route a key prefix to another driver. The most specific matching mount wins. ```ts import { createStorage } from "unstorage"; import fsDriver from "unstorage/drivers/fs"; import redisDriver from "unstorage/drivers/redis"; const storage = createStorage(); storage.mount("files", fsDriver({ base: "./data" })); storage.mount("cache", redisDriver({ base: "my-app" })); await storage.setItem("files:report", "persistent"); await storage.setItem("cache:user:1", { name: "Ada" }); await storage.setItem("temporary", true); // default memory driver ``` `mount(base, driver)` is synchronous and returns the storage instance. A mount receives keys relative to its base. ```ts storage.getMount("cache:user:1"); // { base: "cache:", driver: ... } storage.getMounts("cache"); // [{ base: "cache:", driver: ... }] await storage.unmount("cache"); // disposes the driver by default ``` Pass `false` as the second argument to `unmount` to keep the driver open. ## Watching changes Register a listener for `"update"` and `"remove"` events: ```ts const stop = await storage.watch((event, key) => { console.log(event, key); }); await storage.setItem("status", "ready"); await stop(); ``` Drivers with native watch support can report external changes. For drivers without `watch`, unstorage emits events for individual `setItem`, `setItemRaw`, and `removeItem` calls. Batch writes and `clear` do not currently synthesize events. Call `storage.unwatch()` to remove every listener. ## Cleanup Call `dispose()` when your application shuts down so drivers can close clients, watchers, timers, and other resources: ```ts await storage.dispose(); ``` For the memory driver, disposing also clears its data. ## TypeScript ### Type individual reads `getItem` always includes `null` because a key may not exist. ```ts type User = { name: string }; const user = await storage.getItem("user:1"); // User | null const bytes = await storage.getItemRaw("logo"); // Uint8Array | null ``` ### Type the whole storage ```ts import { createStorage } from "unstorage"; const textStorage = createStorage(); await textStorage.setItem("greeting", "hello"); // await textStorage.setItem("count", 1); // TypeScript error ``` You can define known keys with different value types: ```ts type AppStorage = { items: { theme: "light" | "dark"; visitCount: number; }; }; const appStorage = createStorage(); await appStorage.setItem("theme", "dark"); const visits = await appStorage.getItem("visitCount"); // number | null ``` Unknown string keys still use the general `StorageValue` type. For a typed namespace, use [`prefixStorage`](/guide/utils#namespaces). --- # Utilities > Namespace a storage instance, move snapshots, normalize keys, or add diagnostics tracing. All general utilities are tree-shakable named exports from `unstorage`. ## Namespaces `prefixStorage(storage, prefix)` creates a storage view that automatically prefixes keys. Reads, writes, key listing, batch operations, and watch events are mapped back to the namespace. ```ts import { createStorage, prefixStorage } from "unstorage"; const storage = createStorage(); const assets = prefixStorage(storage, "assets"); await assets.setItem("logo", "logo.svg"); // Equivalent to storage.setItem("assets:logo", "logo.svg") await assets.getKeys(); // ["logo"] ``` A namespace is a view over the original storage, not a separate store. Lifecycle methods and mount-inspection methods are still shared with the original instance, so `prefixStorage` is not a security boundary. You can also provide a value type: ```ts type Post = { title: string; body: string }; const posts = prefixStorage(storage, "posts"); const post = await posts.getItem("welcome"); // Post | null ``` ## Snapshots ### `snapshot(storage, base)` Reads all values under a base into a plain object. The base is removed from the returned keys. ```ts import { snapshot } from "unstorage"; const data = await snapshot(storage, "settings"); // { theme: "dark", "editor:font": "mono" } ``` Pass an empty string to snapshot the whole storage: ```ts const data = await snapshot(storage, ""); ``` ### `restoreSnapshot(storage, data, base?)` Writes a snapshot to a storage instance, optionally under a new base. ```ts import { restoreSnapshot } from "unstorage"; await restoreSnapshot(storage, data, "backup"); ``` ::caution Restoring a snapshot overwrites matching keys but does not remove keys that are absent from the snapshot. :: ## Key helpers Use the same key normalization rules as unstorage when constructing keys dynamically: ```ts import { joinKeys, normalizeKey } from "unstorage"; normalizeKey("/users//1/profile"); // "users:1:profile" joinKeys("users", "1", "profile"); // "users:1:profile" ``` `normalizeBaseKey(base)` is also exported and returns a normalized base with a trailing colon. ## Node.js tracing `withTracing(storage)` wraps storage operations with [Node.js diagnostics channels](https://nodejs.org/api/diagnostics_channel.html). Import it from the dedicated `unstorage/tracing` entry to keep Node-specific tracing out of other bundles. ```ts import { createStorage } from "unstorage"; import { withTracing } from "unstorage/tracing"; const storage = withTracing(createStorage()); await storage.getItem("users:1"); ``` Operations publish to tracing channels such as `unstorage.getItem`, `unstorage.setItem`, and `unstorage.getKeys`. Trace context includes normalized keys and, when available, the selected mount and driver. In runtimes without `node:diagnostics_channel`, `withTracing` returns the storage unchanged. --- # HTTP Server > Expose a storage instance through a Fetch-compatible handler and connect to it with the HTTP driver. ## Create a handler `createStorageHandler` returns a standard `(Request) => Response` fetch handler. It works with servers and runtimes that accept a Fetch API handler. The following example uses [srvx](https://srvx.h3.dev): :pm-install{name="srvx"} ```ts [server.ts] import { serve } from "srvx"; import { createStorage } from "unstorage"; import { createStorageHandler } from "unstorage/server"; import fsDriver from "unstorage/drivers/fs"; const storage = createStorage({ driver: fsDriver({ base: "./data" }), }); const fetch = createStorageHandler(storage, { authorize({ request, key, type }) { const token = request.headers.get("authorization"); if (token !== `Bearer ${process.env.STORAGE_TOKEN}`) { throw new Error(`Unauthorized ${type} for ${key}`); } }, }); await serve({ port: 3000, fetch }); ``` You can pass the same handler to APIs such as `Bun.serve`, `Deno.serve`, or a compatible serverless runtime. ::warning Always authenticate and authorize requests before exposing a storage handler. The protocol supports listing, writing, deleting, and clearing data; an unprotected endpoint grants broad access to the underlying storage. :: ### Options - `authorize(request)`: Runs before every supported operation. It receives `{ request, key, type }`, where `type` is `"read"` or `"write"`. Throw to reject the request. - `resolvePath(request)`: Overrides how the request path is converted to a storage key. It receives the Fetch `Request`. Errors thrown by `authorize` become `401` responses unless the thrown error has a numeric `status` property. ## Connect with the HTTP driver ```ts [client.ts] import { createStorage } from "unstorage"; import httpDriver from "unstorage/drivers/http"; const storage = createStorage({ driver: httpDriver({ base: "http://localhost:3000", headers: { authorization: `Bearer ${process.env.STORAGE_TOKEN}`, }, }), }); await storage.setItem("messages:1", { text: "Hello" }); const message = await storage.getItem("messages:1"); ``` See the [HTTP driver documentation](/drivers/http) for per-request headers, raw values, and TTL options. ## Protocol Paths map to normalized storage keys. A path ending in `/` or `:` is treated as a base for list and clear operations. | Method | Operation | Response | | --------------- | --------------------------------- | --------------------------------- | | `HEAD /key` | `hasItem(key)` and `getMeta(key)` | `200` when found, `404` otherwise | | `GET /key` | `getItem(key)` | Serialized value, or `404` | | `GET /base/` | `getKeys(base)` | JSON array of keys | | `PUT /key` | `setItem(key, body)` | `OK` | | `DELETE /key` | `removeItem(key)` | `OK` | | `DELETE /base/` | `clear(base)` | `OK` | For binary data: - send `Accept: application/octet-stream` with `GET` to use `getItemRaw`; - send `Content-Type: application/octet-stream` with `PUT` to use `setItemRaw`. The HTTP driver sends the `ttl` transaction option as an `x-ttl` header. When metadata contains `mtime` or `ttl`, the handler exposes it through `last-modified`, `x-ttl`, and `cache-control` response headers. --- # 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: ```ts [my-driver.ts] import type { Driver } from "unstorage"; type MyDriverOptions = { initial?: Record; }; export function myDriver( options: MyDriverOptions = {}, ): Driver> { 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: ```ts 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: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](https://github.com/unjs/unstorage/tree/main/src/drivers) 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: ```ts 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](/drivers#driver-dependencies) for the public convention. --- # Drivers > Choose a storage backend without changing the API used by your application. A driver connects unstorage to a specific backend. Pass one as the default driver or [mount it under a key prefix](/guide#mounting-multiple-drivers). ```ts import { createStorage } from "unstorage"; import redisDriver from "unstorage/drivers/redis"; const storage = createStorage({ driver: redisDriver({ base: "my-app" }), }); ``` Without a `driver` option, `createStorage()` uses the [memory driver](/drivers/memory). ## Driver catalog ### Local and runtime storage | Backend | Driver import | Notes | | --- | --- | --- | | [Memory](/drivers/memory) | `unstorage/drivers/memory` | Default, process-local storage with TTL support. | | [Filesystem](/drivers/fs) | `unstorage/drivers/fs` | Node.js filesystem with optional watching. | | [Filesystem Lite](/drivers/fs#nodejs-filesystem-lite) | `unstorage/drivers/fs-lite` | Node.js filesystem without watching dependencies. | | [Browser storage](/drivers/browser) | `unstorage/drivers/localstorage` | `localStorage` or a compatible storage object. | | [Session storage](/drivers/browser) | `unstorage/drivers/session-storage` | Browser `sessionStorage`. | | [IndexedDB](/drivers/browser#indexeddb) | `unstorage/drivers/indexeddb` | Browser database through `idb-keyval`. | | [Capacitor Preferences](/drivers/capacitor-preferences) | `unstorage/drivers/capacitor-preferences` | Mobile preferences and web fallback. | | [Deno KV](/drivers/deno) | `unstorage/drivers/deno-kv` | Deno runtime and Deno Deploy. | | [Deno KV for Node.js](/drivers/deno#usage-nodejs) | `unstorage/drivers/deno-kv-node` | Remote or local Deno KV through `@deno/kv`. | | [LRU Cache](/drivers/lru-cache) | `unstorage/drivers/lru-cache` | Bounded in-process cache. | | [Null](/drivers/null) | `unstorage/drivers/null` | Discards writes; useful for disabling storage. | | [Overlay](/drivers/overlay) | `unstorage/drivers/overlay` | Writable top layer over one or more fallback layers. | ### Databases and managed key-value stores | Backend | Driver import | Notes | | --- | --- | --- | | [SQL with db0](/drivers/database) | `unstorage/drivers/db0` | Works with db0 connectors such as SQLite, PostgreSQL, and libSQL. | | [MongoDB](/drivers/mongodb) | `unstorage/drivers/mongodb` | MongoDB collection-backed storage. | | [PlanetScale](/drivers/planetscale) | `unstorage/drivers/planetscale` | PlanetScale serverless driver. | | [Redis](/drivers/redis) | `unstorage/drivers/redis` | Redis and Redis Cluster through `ioredis`. | | [Upstash Redis](/drivers/upstash) | `unstorage/drivers/upstash` | HTTP-based Upstash Redis client. | ### Cloud and object storage | Backend | Driver imports | Notes | | --- | --- | --- | | [Azure](/drivers/azure) | `unstorage/drivers/azure-app-configuration`, `unstorage/drivers/azure-cosmos`, `unstorage/drivers/azure-key-vault`, `unstorage/drivers/azure-storage-blob`, `unstorage/drivers/azure-storage-table` | App Configuration, Cosmos DB, Key Vault, Blob Storage, and Table Storage. | | [Cloudflare](/drivers/cloudflare) | `unstorage/drivers/cloudflare-cache-binding`, `unstorage/drivers/cloudflare-kv-binding`, `unstorage/drivers/cloudflare-kv-http`, `unstorage/drivers/cloudflare-r2-binding` | Cache, KV, and R2 bindings, plus KV HTTP access. | | [S3-compatible storage](/drivers/s3) | `unstorage/drivers/s3` | Amazon S3, Cloudflare R2, and compatible object stores. | | [Netlify Blobs](/drivers/netlify) | `unstorage/drivers/netlify-blobs` | Netlify deploy and named blob stores. | | [Vercel](/drivers/vercel) | `unstorage/drivers/vercel-runtime-cache` or `unstorage/drivers/vercel-blob` | Runtime cache or persistent blob storage. | | [UploadThing](/drivers/uploadthing) | `unstorage/drivers/uploadthing` | UploadThing file storage. | ### Remote and read-only sources | Backend | Driver import | Notes | | --- | --- | --- | | [HTTP](/drivers/http) | `unstorage/drivers/http` | Any compatible HTTP endpoint, including the unstorage server handler. | | [GitHub](/drivers/github) | `unstorage/drivers/github` | Read-only files from a GitHub repository. | ## Driver dependencies The core package has no runtime dependencies. Drivers that integrate third-party libraries load them lazily on first use, so install only the packages required by your selected drivers. Each driver page includes its install command. Most drivers that load a third-party library accept a `lib` option. Use it when a bundler cannot analyze the dynamic import or when you prefer an explicit import: ```ts import { createStorage } from "unstorage"; import redisDriver from "unstorage/drivers/redis"; import * as ioredis from "ioredis"; const storage = createStorage({ driver: redisDriver({ lib: ioredis, // lib: () => import("ioredis"), // sync or async factory also works }), }); ``` Azure drivers can also accept `identityLib` for `@azure/identity`. Whether it is required depends on the selected authentication method. The db0 driver is different: pass an already configured `database` instance instead of a `lib` module. ### Introspecting dependencies Driver modules export `DRIVER_DEPENDENCIES`. The root package aggregates this information as `builtinDriverDependencies`, which is useful for frameworks and configuration tooling: ```ts import { builtinDriverDependencies } from "unstorage"; const dependencies = builtinDriverDependencies["redis"]; // { lib: { name: "ioredis", version: "..." } } ``` An entry with `optional: true` is needed only for certain features or configurations. For example, `chokidar` is only required by the `fs` driver's `watch()` implementation. Drivers with no third-party dependencies are absent from this map. `name` is the npm package to install. When the driver imports a subpath of it, the entry also has an `import` field with the exact specifier to import (for example `{ name: "uploadthing", import: "uploadthing/server" }`); when `import` is absent, the specifier is `name`. `builtinDrivers` maps every supported driver name to its import specifier: ```ts import { builtinDrivers } from "unstorage"; builtinDrivers["redis"]; // "unstorage/drivers/redis" ``` ## Choosing a driver Before selecting a backend, consider: - **Runtime:** some drivers require Node.js, a browser, or a platform binding. - **Persistence:** memory and runtime caches are intentionally ephemeral. - **Key listing:** some services cannot implement `getKeys` efficiently or at all. - **TTL and metadata:** these are backend-specific capabilities. - **Raw values:** binary support and returned types differ by driver. - **Consistency and limits:** managed services may impose size, latency, or consistency constraints. Check the driver's options and limitations before relying on backend-specific behavior. The common storage API remains the same, but unsupported optional operations may use a fallback or return no results. --- # Azure ## Azure App Configuration Store data in the key-value store of Azure App Configuration. ### Usage **Driver name:** `azure-app-configuration` ::note{to="https://learn.microsoft.com/en-us/azure/azure-app-configuration/overview"} Learn more about Azure App Configuration. :: This driver uses the configuration store as a key-value store. It uses the `key` as the name and the `value` as content. You can also use labels to differentiate between different environments (dev, prod, etc.) and use prefixes to differentiate between different applications (app01, app02, etc.). Install `@azure/app-configuration`. Also install `@azure/identity` when using `DefaultAzureCredential` instead of a connection string. :pm-install{name="@azure/app-configuration"} Usage: ```js import { createStorage } from "unstorage"; import azureAppConfiguration from "unstorage/drivers/azure-app-configuration"; const storage = createStorage({ driver: azureAppConfiguration({ appConfigName: "unstoragetest", label: "dev", prefix: "app01", }), }); ``` **Authentication:** The driver supports the following authentication methods: - **`DefaultAzureCredential`**: This is the recommended way to authenticate. It will use managed identity or environment variables to authenticate the request. It will also work in a local environment by trying to use Azure CLI or Azure PowerShell to authenticate.
⚠️ Make sure that your Managed Identity or personal account has the `App Configuration Data Owner` role assigned to it, even if you already are the `Contributor` or `Owner` on the app configuration resource. - **`connectionString`**: The app configuration connection string. Not recommended for use in production. **Options:** - `appConfigName`: The name of the app configuration resource. - `endpoint`: The endpoint of the app configuration resource. - `connectionString`: The connection string of the app configuration resource. - `prefix`: Optional prefix for keys. This can be used to isolate keys from different applications in the same Azure App Configuration instance. E.g. "app01" results in keys like "app01:foo" and "app01:bar". - `label`: Optional label for keys. If not provided, all keys will be created and listed without labels. This can be used to isolate keys from different environments in the same Azure App Configuration instance. E.g. "dev" results in keys like "foo" and "bar" with the label "dev". ## Azure Cosmos DB Store data in Azure Cosmos DB NoSQL API documents. ### Usage **Driver name:** `azure-cosmos` ::note{to="https://azure.microsoft.com/en-us/services/cosmos-db/"} Learn more about Azure Cosmos DB. :: This driver stores KV information in a NoSQL API Cosmos DB collection as documents. It uses the `id` field as the key and adds `value` and `modified` fields to the document. Install `@azure/cosmos`. Also install `@azure/identity` when using `DefaultAzureCredential` instead of an account key. :pm-install{name="@azure/cosmos"} Usage: ```js import { createStorage } from "unstorage"; import azureCosmos from "unstorage/drivers/azure-cosmos"; const storage = createStorage({ driver: azureCosmos({ endpoint: "ENDPOINT", accountKey: "ACCOUNT_KEY", }), }); ``` **Authentication:** - **`DefaultAzureCredential`**: This is the recommended way to authenticate. It will use managed identity or environment variables to authenticate the request. It will also work in a local environment by trying to use Azure CLI or Azure PowerShell to authenticate.
⚠️ Make sure that your Managed Identity or personal account has at least `Cosmos DB Built-in Data Contributor` role assigned to it. If you already are the `Contributor` or `Owner` on the resource it should also be enough, but that does not accomplish a model of least privilege. - **`accountKey`**: CosmosDB account key. If not provided, the driver will use the DefaultAzureCredential (recommended). **Options:** - **`endpoint`** (required): CosmosDB endpoint in the format of `https://.documents.azure.com:443/`. - `accountKey`: CosmosDB account key. If not provided, the driver will use the DefaultAzureCredential (recommended). - `databaseName`: The name of the database to use. Defaults to `unstorage`. - `containerName`: The name of the container to use. Defaults to `unstorage`. ## Azure Key Vault Store data as Azure Key Vault secrets. ### Usage **Driver name:** `azure-key-vault` ::note{to="https://docs.microsoft.com/en-us/azure/key-vault/secrets/about-secrets"} Learn more about Azure Key Vault secrets. :: This driver stores KV information in Azure Key Vault secrets by using the key as secret id and the value as secret content. Please be aware that key vault secrets don't have the fastest access time and are not designed for high throughput. You also have to disable purge protection for your key vault to be able to delete secrets. This implementation deletes and purges a secret when it is deleted to avoid conflicts with soft delete. ⚠️ Be aware that this driver stores the keys of your `key:value` pairs in an encoded way in Key Vault to avoid conflicts with naming requirements for secrets. This means that you will not be able to access manually (outside of unstorage) created secrets inside your Key Vault, as long as they are not encoded in the same way. To use it, you will need to install `@azure/keyvault-secrets` and `@azure/identity` in your project: :pm-install{name="@azure/keyvault-secrets @azure/identity"} Usage: ```js import { createStorage } from "unstorage"; import azureKeyVault from "unstorage/drivers/azure-key-vault"; const storage = createStorage({ driver: azureKeyVault({ vaultName: "testunstoragevault", }), }); ``` **Authentication:** The driver supports the following authentication methods: - **`DefaultAzureCredential`**: This is the recommended way to authenticate. It will use managed identity or environment variables to authenticate the request. It will also work in a local environment by trying to use Azure CLI or Azure PowerShell to authenticate. ⚠️ Make sure that your Managed Identity or personal account has either the `Key Vault Secrets Officer` (or `Key Vault Secrets User` for read-only) RBAC role assigned or is a member of an access policy that grants `Get`, `List`, `Set`, `Delete` and `Purge` secret permissions. **Options:** - **`vaultName`** (required): The name of the key vault to use. - `serviceVersion`: Version of the Azure Key Vault service to use. Defaults to 7.3. - `pageSize`: The number of entries to retrieve per request. Impacts getKeys() and clear() performance. Maximum value is 25. ## Azure Blob Storage Store data in Azure Blob Storage. ### Usage **Driver name:** `azure-storage-blob` ::note{to="https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob"} Learn more about Azure blob storage. :: The driver uses one container and stores each entry as a separate blob, with the storage key as the blob name. Install `@azure/storage-blob`. Also install `@azure/identity` when using `DefaultAzureCredential`. :pm-install{name="@azure/storage-blob"} Please make sure that the container you want to use exists in your storage account. ```js import { createStorage } from "unstorage"; import azureStorageBlobDriver from "unstorage/drivers/azure-storage-blob"; const storage = createStorage({ driver: azureStorageBlobDriver({ accountName: "myazurestorageaccount", }), }); ``` **Authentication:** The driver supports the following authentication methods: - **`DefaultAzureCredential`**: This is the recommended way to authenticate. It will use managed identity or environment variables to authenticate the request. It will also work in a local environment by trying to use Azure CLI or Azure PowerShell to authenticate.
⚠️ Make sure that your Managed Identity or personal account has the `Storage Blob Data Contributor` role assigned to it, even if you already are `Contributor` or `Owner` on the storage account. - **`AzureNamedKeyCredential`** (only available in Node.js runtime): This will use the `accountName` and `accountKey` to authenticate the request. - **`AzureSASCredential`**: Uses `accountName` and `sasKey` to authenticate the request. - **connection string** (only available in Node.js runtime): This will use the `connectionString` to authenticate the request. This is not recommended as it will expose your account key in plain text. **Options:** - `accountName`: Storage account name. Required unless `connectionString` or `sasUrl` is provided. - `containerName`: The name of the blob container to use. Defaults to `unstorage`. - `accountKey`: The account key to use for authentication. This is only required if you are using `AzureNamedKeyCredential`. - `sasKey`: The SAS token to use for authentication. This is only required if you are using `AzureSASCredential`. - `sasUrl`: The SAS URL of the storage account. This is an alternative to providing `accountName` and `sasKey` separately. The URL can be either: - A storage account URL: `https://.blob.core.windows.net?` - A container URL: `https://.blob.core.windows.net/?` you must specify the `containerName` option - `connectionString`: Storage account connection string for Node.js. - `endpointSuffix`: Storage account endpoint suffix. Needs to be changed for Microsoft Azure operated by 21Vianet, Azure Government or Azurite. Defaults to `.blob.core.windows.net`. ## Azure Table Storage Store data in Azure Table Storage. ### Usage **Driver name:** `azure-storage-table` ::note{to="https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/tables/data-tables"} Learn more about Azure Table storage. :: ::warning This driver is currently not compatible with edge workers like Cloudflare Workers or Vercel Edge Functions. There may be an HTTP-based driver in the future. :: The driver stores all keys in one partition, using the storage key as the `rowKey` and the `unstorageValue` field for values. `getMeta()` returns the entity's last modified time (`mtime`) and `etag`. Install `@azure/data-tables`. Also install `@azure/identity` when using `DefaultAzureCredential` instead of an account key, SAS key, or connection string. :pm-install{name="@azure/data-tables"} Please make sure that the table you want to use exists in your storage account. ```js import { createStorage } from "unstorage"; import azureStorageTableDriver from "unstorage/drivers/azure-storage-table"; const storage = createStorage({ driver: azureStorageTableDriver({ accountName: "myazurestorageaccount", }), }); ``` **Authentication:** The driver supports the following authentication methods: - **`AzureNamedKeyCredential`** (Node.js only): Uses `accountName` and `accountKey`. - **`AzureSASCredential`**: Uses `accountName` and `sasKey`. - **Connection string** (Node.js only): Uses `connectionString`. Keep connection strings secret because they contain account credentials. - **`DefaultAzureCredential`**: Used when none of the above is set. Uses managed identity, environment variables, the Azure CLI, or Azure PowerShell. Install `@azure/identity` and assign the `Storage Table Data Contributor` role. Options are checked in the order above: `accountKey` takes precedence over `sasKey`, which takes precedence over `connectionString`. **Options:** - **`accountName`** (required): Storage account name. - `tableName`: Table name. Defaults to `unstorage`. - `partitionKey`: Partition key shared by all entries. Defaults to `unstorage`. - `accountKey`: Account key for `AzureNamedKeyCredential` authentication. Node.js only. Takes precedence over `sasKey` and `connectionString`. - `sasKey`: SAS key for `AzureSASCredential` authentication. Takes precedence over `connectionString`. - `connectionString`: Connection string for Node.js authentication. - `pageSize`: Entries retrieved per request by `getKeys()` and `clear()`. Defaults to `1000`, which is also the maximum. - `lib`: An imported `@azure/data-tables` module or a function that returns it. - `identityLib`: An imported `@azure/identity` module or a function that returns it. --- # Browser > Store values in `localStorage`, `sessionStorage`, or IndexedDB. These drivers require browser APIs. Avoid creating them during server-side rendering unless you provide a compatible storage object. ## Local storage **Driver import:** `unstorage/drivers/localstorage` ```ts import { createStorage } from "unstorage"; import localStorageDriver from "unstorage/drivers/localstorage"; const storage = createStorage({ driver: localStorageDriver({ base: "my-app" }), }); ``` ### Options - `base`: Prefixes keys to avoid collisions with other applications. - `storage`: A `localStorage`-compatible object. By default, the driver uses `window.localStorage`. - `windowKey`: Selects `"localStorage"` (default) or `"sessionStorage"` from the window object. - `window`: A custom `window` object. Pass it to enable native `storage` event watching. ## Session storage **Driver import:** `unstorage/drivers/session-storage` The session driver has the same options as the local storage driver but selects `window.sessionStorage` by default. ```ts import { createStorage } from "unstorage"; import sessionStorageDriver from "unstorage/drivers/session-storage"; const storage = createStorage({ driver: sessionStorageDriver({ base: "my-app" }), }); ``` ::note Web Storage values are scoped to the browser origin. `sessionStorage` is additionally scoped to the current tab and is cleared when that tab's session ends. :: ## IndexedDB **Driver import:** `unstorage/drivers/indexeddb` The IndexedDB driver uses [`idb-keyval`](https://github.com/jakearchibald/idb-keyval). :pm-install{name="idb-keyval"} ```ts import { createStorage } from "unstorage"; import indexedDBDriver from "unstorage/drivers/indexeddb"; const storage = createStorage({ driver: indexedDBDriver({ base: "my-app", dbName: "my-app-db", storeName: "keyval", }), }); ``` ### Options - `base`: Prefixes all keys. - `dbName`: Custom database name. Set it together with `storeName`. - `storeName`: Custom object store name. Set it together with `dbName`. - `lib`: An imported `idb-keyval` module or a function that returns it. See [Driver dependencies](/drivers#driver-dependencies). When `dbName` and `storeName` are omitted, `idb-keyval` uses its default `keyval-store` database and `keyval` object store. The regular API stores serialized values. Use the experimental raw API to preserve IndexedDB-native structured clone values: ```ts await storage.setItemRaw("profile", { name: "Ada" }); const profile = await storage.getItemRaw("profile"); ``` --- # Capacitor Preferences > Store data via Capacitor Preferences API on mobile devices or local storage on the web. ::read-more{to="https://capacitorjs.com/docs/apis/preferences"} Learn more about Capacitor Preferences API. :: ## Usage **Driver name:** `capacitor-preferences` Install and sync `@capacitor/preferences` in your Capacitor project: :pm-install{name="@capacitor/preferences"} :pm-x{command="cap sync"} ```js import { createStorage } from "unstorage"; import capacitorPreferences from "unstorage/drivers/capacitor-preferences"; const storage = createStorage({ driver: capacitorPreferences({ base: "test", }), }); ``` **Options:** - `base`: Prefixes all keys to avoid collisions. - `lib`: An imported `@capacitor/preferences` module or a function that returns it. --- # Cloudflare > Use Cloudflare Cache, KV, or R2 from Workers, or access KV through the HTTP API. ## Cache API (binding) > Cache data inside Cloudflare Workers with the runtime Cache API. **Driver import:** `unstorage/drivers/cloudflare-cache-binding` ```ts import { createStorage } from "unstorage"; import cloudflareCacheDriver from "unstorage/drivers/cloudflare-cache-binding"; const storage = createStorage({ driver: cloudflareCacheDriver({ base: "my-app", ttl: 3600, }), }); ``` Options: - `base`: Prefixes all cache keys. - `ttl`: Default TTL in seconds. - `name`: Uses a named cache from `caches.open(name)` instead of `caches.default`. Workers for Platforms namespaced scripts require a named cache. Pass `ttl` or `tag` per write to set `Cache-Control` or `Cache-Tag`: ```ts await storage.setItem("page:home", "...", { ttl: 60, tag: "pages", }); ``` ::note The Cache API cannot list keys. `getKeys()` returns an empty array, so clearing by base is not supported. :: ## Cloudflare KV (binding) > Store data in Cloudflare KV and access from worker bindings. ### Usage **Driver name:** `cloudflare-kv-binding` ::read-more{to="https://developers.cloudflare.com/workers/runtime-apis/kv"} Learn more about Cloudflare KV. :: This driver only works in a Cloudflare Workers environment. Use `cloudflare-kv-http` in other runtimes. You need to create and assign a KV. See [KV Bindings](https://developers.cloudflare.com/workers/runtime-apis/kv#kv-bindings) for more information. ```ts import { createStorage } from "unstorage"; import cloudflareKVBindingDriver from "unstorage/drivers/cloudflare-kv-binding"; export default { async fetch(_request: Request, env: Env) { const storage = createStorage({ driver: cloudflareKVBindingDriver({ binding: env.STORAGE }), }); return Response.json(await storage.getKeys()); }, }; ``` **Options:** - `binding`: KV namespace binding or a global binding name. Defaults to `STORAGE`. - `base`: Prefixes all stored keys. - `minTTL`: Minimum TTL in seconds. Defaults to Cloudflare's minimum of `60`. ## Cloudflare KV (http) > Store data in Cloudflare KV using the Cloudflare API v4. ### Usage **Driver name:** `cloudflare-kv-http` ::read-more{to="https://developers.cloudflare.com/api/operations/workers-kv-namespace-list-namespaces"} Learn more about Cloudflare KV API. :: You need to create a KV namespace. See [KV Bindings](https://developers.cloudflare.com/workers/runtime-apis/kv#kv-bindings) for more information. This driver uses native `fetch` and works across runtimes. Inside Cloudflare Workers, prefer `cloudflare-kv-binding` for direct binding access. ```ts import { createStorage } from "unstorage"; import cloudflareKVHTTPDriver from "unstorage/drivers/cloudflare-kv-http"; const storage = createStorage({ driver: cloudflareKVHTTPDriver({ accountId: "my-account-id", namespaceId: "my-kv-namespace-id", apiToken: process.env.CLOUDFLARE_API_TOKEN!, }), }); ``` **Options:** - `accountId`: Cloudflare account ID. - `namespaceId`: The ID of the KV namespace to target. **Note:** be sure to use the namespace's ID, and not the name or binding used in a worker environment. - `apiToken`: API Token generated from the [User Profile 'API Tokens' page](https://dash.cloudflare.com/profile/api-tokens). - `email`: Email address associated with your account. May be used along with `apiKey` to authenticate in place of `apiToken`. - `apiKey`: API key generated on the "My Account" page of the Cloudflare console. May be used along with `email` to authenticate in place of `apiToken`. - `userServiceKey`: A special Cloudflare API key good for a restricted set of endpoints. Always begins with "v1.0-", may vary in length. May be used to authenticate in place of `apiToken` or `apiKey` and `email`. - `apiURL`: Custom API URL. Defaults to `https://api.cloudflare.com`. - `base`: Prefixes all stored keys. - `minTTL`: Minimum TTL in seconds. Defaults to Cloudflare's minimum of `60`. **Transaction options:** - `ttl`: Supported for `setItem(key, value, { ttl: number /* seconds min 60 */ })` **Supported methods:** - `getItem`: `GET /values/:key` - `hasItem`: `GET /metadata/:key` - `setItem`: `PUT /values/:key` - `removeItem`: `DELETE /values/:key` - `getKeys`: `GET /keys` - `clear`: Lists keys, then sends chunks of up to 10,000 keys with `POST /bulk/delete`. ## Cloudflare R2 (binding) > Store data in Cloudflare R2 buckets and access from worker bindings. ::warning This experimental driver requires a Cloudflare Workers R2 binding. For other runtimes, use the [S3 driver](/drivers/s3) with an R2 S3-compatible endpoint. :: ### Usage **Driver name:** `cloudflare-r2-binding` ::read-more{to="https://developers.cloudflare.com/r2/api/workers/workers-api-reference/"} Learn more about Cloudflare R2 buckets. :: You need to create and assign a R2 bucket. See [R2 Bindings](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/#create-a-binding) for more information. ```ts import { createStorage } from "unstorage"; import cloudflareR2BindingDriver from "unstorage/drivers/cloudflare-r2-binding"; export default { async fetch(_request: Request, env: Env) { const storage = createStorage({ driver: cloudflareR2BindingDriver({ binding: env.BUCKET }), }); return Response.json(await storage.getKeys()); }, }; ``` **Options:** - `binding`: Bucket binding or name. Default is `BUCKET`. - `base`: Prefix all keys with base. **Transaction options:** - `getItemRaw(key, { type: "..." })` - `type: "object"`: Return the [R2 object body](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/#r2objectbody-definition). - `type: "stream"`: Return body stream. - `type: "blob"`: Return a `Blob`. - `type: "bytes"`: Return a `Uint8Array`. - `type: "arrayBuffer"`: Return an `ArrayBuffer` (default) ## Cloudflare R2 (http) To use Cloudflare R2 over HTTP, configure the [S3 driver](/drivers/s3) with your R2 endpoint and set `region` to `auto`. --- # SQL Database > Store data in a supported SQL database through db0. ## Usage **Driver name:** `db0` This driver stores key-value data through [db0](https://db0.unjs.io). It supports db0 connections using the `sqlite`, `libsql`, `postgresql`, and `mysql` dialects. ::warning Database driver is experimental and behavior may change in the future. :: To use, you will need to install `db0` in your project: :pm-install{name="db0"} Select and configure the appropriate connector for your database. ::important{to="https://db0.unjs.io/connectors"} Learn more about configuring connectors in the `db0` documentation. :: You can then configure the driver like this: ```js import { createDatabase } from "db0"; import { createStorage } from "unstorage"; import dbDriver from "unstorage/drivers/db0"; import sqlite from "db0/connectors/better-sqlite3"; // Learn more: https://db0.unjs.io const database = createDatabase(sqlite({/* db0 connector options */})); const storage = createStorage({ driver: dbDriver({ database, tableName: "custom_table_name", // Default is "unstorage" }), }); ``` ::tip No manual schema setup is required. Before the first operation, the driver creates a table with `key`, `value`, `blob`, `created_at`, and `updated_at` columns when it does not already exist. :: **Options:** - **`database`** (required): A `db0` database instance. - `tableName`: The name of the table to use. It defaults to `unstorage`. --- # Deno KV > Store data in Deno KV ::note{to="https://deno.com/kv"} Learn more about Deno KV. :: ## Usage (Deno) **Driver name:** `deno-kv` ::important `deno-kv` driver requires [Deno deploy](https://docs.deno.com/deploy/kv/manual/on_deploy/) or [Deno runtime](https://docs.deno.com/runtime/) with `--unstable-kv` CLI flag. See [Node.js](#usage-nodejs) section for other runtimes. :: ::note The driver automatically maps Unstorage keys to Deno. For example, `"test:key"` key will be mapped to `["test", "key"]` and vice versa. :: ```js import { createStorage } from "unstorage"; import denoKVdriver from "unstorage/drivers/deno-kv"; const storage = createStorage({ driver: denoKVdriver({ // path: ":memory:", // base: "", // ttl: 60, // in seconds }), }); ``` **Options:** - `path`: (optional) File system path to where you'd like to store your database, otherwise one will be created for you based on the current working directory of your script by Deno. You can pass `:memory:` for testing. - `base`: (optional) Prefix key added to all operations. - `openKv`: Optional custom function that returns a Deno KV instance. - `ttl`: (optional) Default TTL for all items in seconds. **Per-call options:** - `ttl`: Add TTL (in seconds) for this `setItem` call. ::note Expiration is not strictly enforced by Deno: keys may persist after their expire time. For strict expiry, store the timestamp in your value and check it after retrieval. See [Deno KV Key Expiration](https://docs.deno.com/deploy/kv/manual/key_expiration/) for more information. :: ## Usage (Node.js) **Driver name:** `deno-kv-node` Deno provides [`@deno/kv`](https://www.npmjs.com/package/@deno/kv) npm package, A Deno KV client library optimized for Node.js. - Access [Deno Deploy](https://deno.com/deploy) remote databases (or any endpoint implementing the open [KV Connect](https://github.com/denoland/denokv/blob/main/proto/kv-connect.md) protocol) on Node 18+. - Create local KV databases backed by [SQLite](https://www.sqlite.org/index.html), using optimized native [NAPI](https://nodejs.org/docs/latest-v18.x/api/n-api.html) packages for Node - compatible with databases created by Deno itself. - Create ephemeral in-memory KV instances backed by SQLite memory files or by a lightweight JS-only implementation for testing. Install the `@deno/kv` dependency: :pm-install{name="@deno/kv"} ```js import { createStorage } from "unstorage"; import denoKVNodedriver from "unstorage/drivers/deno-kv-node"; const storage = createStorage({ driver: denoKVNodedriver({ // path: ":memory:", // base: "", }), }); ``` **Options:** - `path`: File path, remote URL, or `:memory:` value supported by `@deno/kv`. - `base`: Prefix added to all keys. - `openKvOptions`: See the [`@deno/kv` API documentation](https://www.npmjs.com/package/@deno/kv#api) for available options. --- # Filesystem (Node.js) > Store data in the filesystem using Node.js API. ## Usage **Driver name:** `fs` or `fs-lite` Maps data to the real filesystem using directory structure for nested keys. Supports watching using [chokidar](https://github.com/paulmillr/chokidar). Watching requires `chokidar` to be installed (all other operations work without it): :pm-install{name="chokidar"} This driver implements meta for each key including `mtime` (last modified time), `atime` (last access time), and `size` (file size) using `fs.stat`. ```js import { createStorage } from "unstorage"; import fsDriver from "unstorage/drivers/fs"; const storage = createStorage({ driver: fsDriver({ base: "./tmp" }), }); ``` **Options:** - `base` (**required**): Directory used as the storage root. - `ignore`: Glob patterns ignored by watching and key listing. - `readOnly`: Disables write and removal operations. - `noClear`: Disables clearing. - `atomic`: Write items atomically (see [below](#atomic-writes)). Disabled by default. - `watchOptions`: Additional [chokidar](https://github.com/paulmillr/chokidar) options. - `lib`: An imported `chokidar` module or a function that returns it. ## Node.js Filesystem (Lite) This driver uses pure Node.js API without extra dependencies. ```js import { createStorage } from "unstorage"; import fsLiteDriver from "unstorage/drivers/fs-lite"; const storage = createStorage({ driver: fsLiteDriver({ base: "./tmp" }), }); ``` **Options:** - `base` (**required**): Directory used as the storage root. - `ignore`: Optional callback `(path: string) => boolean`. - `readOnly`: Disables write and removal operations. - `noClear`: Disables clearing. - `atomic`: Write items atomically (see [below](#atomic-writes)). Disabled by default. ## Atomic writes By default items are written directly to their destination file. A reader that opens a key while it is being written can therefore observe a partially written value, and a failed write can leave a truncated file behind. Enabling `atomic` writes each item to a temporary file in the same directory and renames it over the destination, so readers only ever see the complete previous or the complete new value: ```js const storage = createStorage({ driver: fsDriver({ base: "./tmp", atomic: true }), }); ``` It is opt-in because renaming replaces the destination file rather than updating it in place: - Ownership, ACLs and extended attributes are not carried over (the file mode is). - A key that is a symbolic link is replaced by a regular file instead of being written through. - Hard links to a key stop tracking it after the next write. - Small writes are around twice as slow. Large writes are unaffected or slightly faster. - On Windows, writing a key that another process is holding open can fail with `EPERM`. Atomic writes protect against interleaved readers, not against power loss: the data is not flushed to disk before the rename. --- # GitHub > Read files from a remote GitHub repository. ## Usage **Driver name:** `github` This read-only driver fetches the repository file list and caches it for 10 minutes by default. Providing a token is strongly recommended to avoid GitHub API rate limits. File contents are fetched separately from the raw content URL, using the same token. ```js import { createStorage } from "unstorage"; import githubDriver from "unstorage/drivers/github"; const storage = createStorage({ driver: githubDriver({ repo: "nuxt/nuxt", branch: "main", dir: "/docs", }), }); ``` **Options:** - `repo` (**required**): Repository in `owner/name` format. - `token` (recommended): GitHub API token. - `branch`: Target branch. Defaults to `main`. - `dir`: Directory to use as the driver root. - `ttl`: File-list cache duration in seconds. Defaults to `600` (10 minutes). - `apiURL`: GitHub API base URL. Defaults to `https://api.github.com`. - `cdnURL`: Raw content base URL. Defaults to `https://raw.githubusercontent.com`. ## Private repositories To read a **private** repository, provide a GitHub access token via the `token` option. The same token is used both to list the keys (GitHub API) and to fetch file contents (raw CDN), so it needs read access to the repository's contents. Either token type works: - **Fine-grained token** ([recommended](https://github.blog/security/application-security/introducing-fine-grained-personal-access-tokens-for-github/)): grant it read-only **Contents** access, limited to the target repository. - **Classic token** ([settings](https://github.com/settings/tokens)): grant it the `repo` scope. Read the token from an environment variable instead of hard-coding it: ```js import { createStorage } from "unstorage"; import githubDriver from "unstorage/drivers/github"; const storage = createStorage({ driver: githubDriver({ repo: "username/private-repo", branch: "main", token: process.env.GITHUB_TOKEN, }), }); ``` ::note GitHub Apps are not supported — use a personal access token. :: --- # HTTP > Use a remote HTTP endpoint through the unstorage API. **Driver import:** `unstorage/drivers/http` The driver is designed for the built-in [storage server protocol](/guide/http-server), but it can connect to any endpoint that implements the same methods and response formats. ```ts import { createStorage } from "unstorage"; import httpDriver from "unstorage/drivers/http"; const storage = createStorage({ driver: httpDriver({ base: "https://storage.example.com", headers: { authorization: `Bearer ${process.env.STORAGE_TOKEN}`, }, }), }); ``` ## Options - `base` (**required**): Base URL of the storage endpoint. - `headers`: Headers sent with every request. ## Per-operation options - `headers`: Additional headers for one operation. These override driver-level headers. - `ttl`: TTL in seconds. The driver sends it as `x-ttl`. ```ts await storage.setItem( "sessions:1", { userId: 1 }, { ttl: 3600, headers: { "x-request-id": "abc" }, }, ); ``` ## Protocol mapping | Storage method | HTTP request | | ------------------------ | -------------------------------------------------------- | | `hasItem(key)` | `HEAD /key` | | `getItem(key)` | `GET /key` | | `getItemRaw(key)` | `GET /key` with `Accept: application/octet-stream` | | `getMeta(key)` | `HEAD /key` | | `setItem(key, value)` | `PUT /key` | | `setItemRaw(key, value)` | `PUT /key` with `Content-Type: application/octet-stream` | | `removeItem(key)` | `DELETE /key` | | `getKeys(base)` | `GET /base/:` | | `clear(base)` | `DELETE /base/:` | `getItem` returns the response body as text, which the storage layer then deserializes. `getItemRaw` returns an `ArrayBuffer`. `getMeta` maps the `last-modified` header to `mtime`, the `x-ttl` header to `ttl`, and includes the HTTP response `status`. A `404` response becomes `null` for `getItem` and `getItemRaw`, or `false` for `hasItem`. Other non-success responses throw an error. --- # LRU Cache > Keeps cached data in memory using LRU Cache. ## Usage **Driver name:** `lru-cache` Keeps cached data in memory using [LRU Cache](https://www.npmjs.com/package/lru-cache). Make sure to install the required dependency: :pm-install{name="lru-cache"} See [`lru-cache`](https://www.npmjs.com/package/lru-cache) for supported options. By default, [`max`](https://www.npmjs.com/package/lru-cache#max) setting is set to `1000` items. A default behavior for [`sizeCalculation`](https://www.npmjs.com/package/lru-cache#sizecalculation) option is implemented based on buffer size of both key and value. ```js import { createStorage } from "unstorage"; import lruCacheDriver from "unstorage/drivers/lru-cache"; const storage = createStorage({ driver: lruCacheDriver(), }); ``` --- # Memory > Keep values in the current JavaScript process. The memory driver uses a `Map` and is the default for `createStorage()`. Data is not shared between processes and is lost when the process exits or the storage is disposed. ```ts import { createStorage } from "unstorage"; const storage = createStorage(); ``` You can also create the driver explicitly: ```ts import { createStorage } from "unstorage"; import memoryDriver from "unstorage/drivers/memory"; const driver = memoryDriver(); const storage = createStorage({ driver }); const map = driver.getInstance?.(); ``` ## TTL Pass a TTL in seconds when setting an item: ```ts await storage.setItem("session:1", { userId: 1 }, { ttl: 60 }); ``` The driver removes the item after the TTL expires. Calling `clear()` or `dispose()` also cancels pending expiration timers. Use this driver for tests, short-lived caches, and local defaults—not for durable or distributed data. --- # MongoDB > Store data in MongoDB using Node.js MongoDB package. ## Usage **Driver name:** `mongodb` ::read-more{to="https://www.mongodb.com/"} Learn more about MongoDB. :: This driver stores KV information in a MongoDB collection with a separate document for each key value pair. To use it, you will need to install `mongodb` in your project: :pm-install{name="mongodb"} Usage: ```js import { createStorage } from "unstorage"; import mongodbDriver from "unstorage/drivers/mongodb"; const storage = createStorage({ driver: mongodbDriver({ connectionString: "CONNECTION_STRING", databaseName: "test", collectionName: "test", }), }); ``` **Authentication:** The driver supports the following authentication methods: - **`connectionString`**: The MongoDB connection string. This is the only way to authenticate. **Options:** - **`connectionString`** (required): The connection string to use to connect to the MongoDB database. It should be in the format `mongodb://:@:/`. - `databaseName`: The name of the database to use. Defaults to `unstorage`. - `collectionName`: The name of the collection to use. Defaults to `unstorage`. - `clientOptions`: Optional configuration settings for the MongoClient instance. --- # Netlify Blobs > Store data in Netlify Blobs. Store data in a [Netlify Blobs](https://docs.netlify.com/blobs/overview/) store. This is supported in both [edge](#using-in-netlify-edge-functions) and Node.js function runtimes, as well as during builds. ::read-more{title="Netlify Blobs" to="https://docs.netlify.com/blobs/overview/"} :: ## Usage **Driver name:** `netlify-blobs` Make sure to install the required dependency: :pm-install{name="@netlify/blobs"} ```js import { createStorage } from "unstorage"; import netlifyBlobsDriver from "unstorage/drivers/netlify-blobs"; const storage = createStorage({ driver: netlifyBlobsDriver({ name: "blob-store-name", }), }); ``` You can create a deploy-scoped store by setting `deployScoped` option to `true`. This will mean that the deploy only has access to its own store. The store is managed alongside the deploy, with the same deploy previews, deletes, and rollbacks. This is required during builds, which only have access to deploy-scoped stores. ```js import { createStorage } from "unstorage"; import netlifyBlobsDriver from "unstorage/drivers/netlify-blobs"; const storage = createStorage({ driver: netlifyBlobsDriver({ deployScoped: true, }), }); ``` To use, you will need to install `@netlify/blobs` as dependency or devDependency in your project: ```json { "devDependencies": { "@netlify/blobs": "latest" } } ``` **Options:** - `name` - The name of the store to use. It is created if needed. This is required except for deploy-scoped stores. - `deployScoped` - If set to `true`, the store is scoped to the deploy. This means that it is only available from that deploy, and will be deleted or rolled-back alongside it. - `consistency` - The [consistency model](https://docs.netlify.com/blobs/overview/#consistency) to use for the store. This can be `eventual` or `strong`. Default is `eventual`. - `siteID` - Required during builds, where it is available as `constants.SITE_ID`. At runtime this is set automatically. - `token` - Required during builds, where it is available as `constants.NETLIFY_API_TOKEN`. At runtime this is set automatically. **Advanced options:** These are not normally needed, but are available for advanced use cases or for use in unit tests. - `apiURL` - `edgeURL` - `uncachedEdgeURL` ## Using in Netlify edge functions When using Unstorage in a Netlify edge function you should use a URL import. This does not apply if you are compiling your code in a framework - just if you are creating your own edge functions. ```ts import { createStorage } from "https://esm.sh/unstorage"; import netlifyBlobsDriver from "https://esm.sh/unstorage/drivers/netlify-blobs"; export default async function handler(request: Request) { const storage = createStorage({ driver: netlifyBlobsDriver({ name: "blob-store-name", }), }); // ... } ``` ## Updating stores from Netlify Blobs beta There has been a change in the way global blob stores are stored in `@netlify/blobs` version `7.0.0` which means that you will not be able to access objects in global stores created by older versions until you migrate them. This does not affect deploy-scoped stores, nor does it affect objects created with the new version. You can migrate objects in your old stores by running the following command in the project directory using the latest version of the Netlify CLI: ```sh netlify recipes blobs-migrate ``` --- # Null > Disable storage by discarding every write. The null driver behaves like [`/dev/null`](https://en.wikipedia.org/wiki/Null_device): writes succeed without storing data, reads return `null`, `hasItem` returns `false`, and `getKeys` returns an empty array. ```ts import { createStorage } from "unstorage"; import nullDriver from "unstorage/drivers/null"; const storage = createStorage({ driver: nullDriver(), }); ``` It is useful as an explicit no-op backend when storage or caching is optional. --- # Overlay > Add a writable layer over one or more fallback drivers. The overlay reads layers in order and returns the first matching value. All writes go to the first layer, so lower layers remain unchanged. ```ts import { createStorage } from "unstorage"; import fsDriver from "unstorage/drivers/fs"; import memoryDriver from "unstorage/drivers/memory"; import overlayDriver from "unstorage/drivers/overlay"; const storage = createStorage({ driver: overlayDriver({ layers: [ memoryDriver(), // writable top layer fsDriver({ base: "./data" }), // read-only through the overlay ], }), }); ``` Setting a value only changes the memory layer. Removing a key writes an internal tombstone to the first layer so a value with the same key in a lower layer stays hidden. ```ts await storage.setItem("config:theme", "dark"); await storage.removeItem("defaults:locale"); ``` ::caution The overlay uses the reserved string `__OVERLAY_REMOVED__` as its tombstone. Do not store that exact string as an application value in an overlay layer. :: ## Limitations - Put a writable driver first if the overlay will receive writes. - Native raw values, metadata, and backend watching are not implemented. - Clearing uses the storage fallback and writes tombstones for visible keys, which can be expensive. - Disposing the overlay disposes every layer. --- # PlanetScale > Store data in MySQL database via PlanetScale. ## Usage **Driver name:** `planetscale` ::read-more{to="https://planetscale.com/"} Learn more about PlanetScale. :: This driver stores KV information in a Planetscale DB with columns of `id`, `value`, `created_at` and `updated_at`. To use, you will need to install `@planetscale/database` in your project: :pm-install{name="@planetscale/database"} Then you can create a table to store your data by running the following query in your Planetscale database, where `` is the name of the table you want to use: ``` create table ( id varchar(255) not null primary key, value longtext, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp on update current_timestamp ); ``` You can then configure the driver like this: ```js import { createStorage } from "unstorage"; import planetscaleDriver from "unstorage/drivers/planetscale"; const storage = createStorage({ driver: planetscaleDriver({ // This should certainly not be inlined in your code but loaded via runtime config // or environment variables depending on your framework/project. url: "mysql://xxxxxxxxx:************@xxxxxxxxxx.us-east-3.psdb.cloud/my-database?sslaccept=strict", // table: 'storage' }), }); ``` **Options:** - **`url`** (required): You can find your URL in the [Planetscale dashboard](https://planetscale.com/docs/tutorials/connect-nodejs-app). - `table`: The name of the table to read from. It defaults to `storage`. - `boostCache`: Whether to enable cached queries: See [docs](https://planetscale.com/docs/concepts/query-caching-with-planetscale-boost#using-cached-queries-in-your-application). --- # Redis > Store data in a Redis. ## Usage **Driver name:** `redis` ::read-more{to="https://redis.com"} Learn more about Redis. :: ::note Unstorage uses [`ioredis`](https://github.com/redis/ioredis) internally to connect to Redis. :: To use it, you will need to install `ioredis` in your project: :pm-install{name="ioredis"} Usage with single Redis instance: ```ts import { createStorage } from "unstorage"; import redisDriver from "unstorage/drivers/redis"; const storage = createStorage({ driver: redisDriver({ base: "unstorage", host: "HOSTNAME", tls: true as any, port: 6380, password: "REDIS_PASSWORD", }), }); ``` Usage with a Redis cluster (e.g. AWS ElastiCache or Azure Redis Cache): ⚠️ If you connect to a cluster, when running commands that operate over multiple keys, all keys must be part of the same hashslot. Otherwise you may encounter the Redis error `CROSSSLOT Keys in request don't hash to the same slot`. You should use [`hashtags`](https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/#hash-tags) to control how keys are slotted. If you want all keys to hash to the same slot, you can include the hashtag in the base prefix by wrapping it in curly braces. Read more about [Clustering Best Practices](https://redis.io/blog/redis-clustering-best-practices-with-keys/). ```js const storage = createStorage({ driver: redisDriver({ base: "{unstorage}", cluster: [ { port: 6380, host: "HOSTNAME", }, ], clusterOptions: { redisOptions: { tls: { servername: "HOSTNAME" }, password: "REDIS_PASSWORD", }, }, }), }); ``` **Options:** - `base`: Optional prefix to use for all keys. Can be used for namespacing. Has to be used as a hashtag prefix for redis cluster mode. - `url`: Url to use for connecting to redis. Takes precedence over `host` option. Has the format `redis://:@:` - `cluster`: List of redis nodes to use for cluster mode. Takes precedence over `url` and `host` options. - `clusterOptions`: Options to use for cluster mode. - `ttl`: Default TTL for all items in **seconds**. - `scanCount`: How many keys to scan at once ([redis documentation](https://redis.io/docs/latest/commands/scan/#the-count-option)). - `preConnect`: Whether to initialize the redis instance immediately. Otherwise, it will be initialized on the first read/write call. Default: `false`. See [ioredis](https://github.com/redis/ioredis/blob/master/API.md#new-redisport-host-options) for all available options. **Transaction options:** - `ttl`: Supported for `setItem(key, value, { ttl: number /* seconds */ })` --- # S3 > Store data in Amazon S3 or another S3-compatible object store. The driver uses [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and request signing from `aws4fetch`, so it works in Node.js and edge runtimes. ## Usage **Driver name:** `s3` ### Setup Create a bucket with your provider and collect the following values: - Access Key ID - Secret Access Key - Bucket name - Endpoint - Region Make sure to install the required dependency: :pm-install{name="aws4fetch"} Configure the required driver options: ```ts import { createStorage } from "unstorage"; import s3Driver from "unstorage/drivers/s3"; const storage = createStorage({ driver: s3Driver({ accessKeyId: "", // Access Key ID secretAccessKey: "", // Secret Access Key endpoint: "", bucket: "", region: "", }), }); ``` **Options:** - `accessKeyId` (**required**): Access key ID. - `secretAccessKey` (**required**): Secret access key. - `endpoint` (**required**): S3-compatible service endpoint. - `bucket` (**required**): Bucket name. - `region` (**required**): Bucket region; use `auto` for Cloudflare R2. - `bulkDelete`: Uses the bulk delete API to speed up `clear()` (default: `true`). Set it to `false` when the provider does not implement [DeleteObjects](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html). - `lib`: An imported `aws4fetch` module or a function that returns it. ## Setting Headers You can specify HTTP headers when storing items using the options parameter: ```ts // Set Content-Type and Cache-Control await storage.setItemRaw("image.png", imageBuffer, { headers: { "Content-Type": "image/png", "Cache-Control": "max-age=31536000", }, }); // Set custom S3 metadata await storage.setItem("document.json", jsonString, { headers: { "Content-Type": "application/json", "x-amz-meta-author": "john-doe", }, }); ``` Supported headers include: - `Content-Type` - `Cache-Control` - `Content-Disposition` - `Content-Encoding` - `Content-Language` - `Expires` - Custom metadata via `x-amz-meta-*` prefixed headers > **Note:** `getMeta()` only returns custom metadata headers (those with `x-amz-meta-*` prefix). Standard headers like `Content-Type` are set on the S3 object but not returned by `getMeta()`. ## Tested providers Any standards-compatible S3 provider should work. Contributions documenting additional tested providers are welcome. ### Amazon S3 :read-more{to="https://aws.amazon.com/s3/" title="Amazon S3"} Options: - Set `endpoint` to `https://s3.[region].amazonaws.com/` ### Cloudflare R2 :read-more{to="https://www.cloudflare.com/developer-platform/products/r2/" title="Cloudflare R2"} Options: - Set `endpoint` to `https://[uid].r2.cloudflarestorage.com/` - Set `region` to `auto` --- # UploadThing > Store data using UploadThing. ::note{to="https://uploadthing.com/"} Learn more about UploadThing. :: ::warning UploadThing support is experimental. A deleted key currently cannot be reused; follow the [upstream issue](https://github.com/pingdotgg/uploadthing/issues/948) for updates. :: ## Usage **Driver name:** `uploadthing` Install the `uploadthing` dependency: :pm-install{name="uploadthing"} ```js import { createStorage } from "unstorage"; import uploadthingDriver from "unstorage/drivers/uploadthing"; const storage = createStorage({ driver: uploadthingDriver({ // token: "", // UPLOADTHING_SECRET environment variable will be used if not provided. }), }); ``` **Options:** - `token`: UploadThing API token. When omitted, UploadThing uses its supported environment configuration. - `base`: Optional prefix for all keys. - `lib`: An imported `uploadthing/server` module or a function that returns it. --- # Upstash > Store data in an Upstash Redis database. ## Usage **Driver name:** `upstash` ::read-more{to="https://upstash.com/"} Learn more about Upstash. :: ::note Unstorage uses [`@upstash/redis`](https://github.com/upstash/upstash-redis) internally to connect to Upstash Redis. :: To use it, you will need to install `@upstash/redis` in your project: :pm-install{name="@upstash/redis"} Usage with Upstash Redis: ```js import { createStorage } from "unstorage"; import upstashDriver from "unstorage/drivers/upstash"; const storage = createStorage({ driver: upstashDriver({ base: "unstorage", // url: "", // or set UPSTASH_REDIS_REST_URL env // token: "", // or set UPSTASH_REDIS_REST_TOKEN env }), }); ``` **Options:** - `base`: Optional prefix to use for all keys. Can be used for namespacing. - `url`: The REST URL for your Upstash Redis database. Find it in [the Upstash Redis console](https://console.upstash.com/redis/). Driver uses `UPSTASH_REDIS_REST_URL` environment by default. - `token`: The REST token for authentication with your Upstash Redis database. Find it in [the Upstash Redis console](https://console.upstash.com/redis/). Driver uses `UPSTASH_REDIS_REST_TOKEN` environment by default. - `ttl`: Default TTL for all items in **seconds**. - `scanCount`: How many keys to scan at once. See [@upstash/redis documentation](https://upstash.com/docs/redis/sdks/ts/overview) for all available options. **Transaction options:** - `ttl`: Supported for `setItem(key, value, { ttl: number /* seconds */ })` --- # Vercel ## Vercel Runtime Cache > Cache data within Vercel Functions using the Runtime Cache API. ::read-more{to="https://vercel.com/docs/functions"} Learn more about Vercel Functions and Runtime Cache. :: ### Usage **Driver name:** `vercel-runtime-cache` ```js import { createStorage } from "unstorage"; import vercelRuntimeCacheDriver from "unstorage/drivers/vercel-runtime-cache"; const storage = createStorage({ driver: vercelRuntimeCacheDriver({ // base: "app", // ttl: 60, // seconds tags: ["v1"], }), }); ``` **Optional step:** To allow using outside of vercel functions, install `@vercel/functions` in your project: :pm-install{name="@vercel/functions"} ### Options - `base`: Optional prefix to use for all keys (namespacing). - `ttl`: Default TTL for all items in seconds. - `tags`: Default tags to apply to all cache entries (Note: Will be merged with per-call option tags). ### Per-call options - `ttl`: Add TTL (in seconds) for this `setItem` call. - `tags`: Apply tags to this `setItem` call. **Example:** ```js await storage.setItem("user:123", JSON.stringify({ name: "Ana" }), { ttl: 3600, tags: ["user:123"], }); ``` **To expire by tags:** ```js await storage.clear(); ``` ### Limitations - `getKeys`: The runtime cache API does not support listing keys; this returns `[]`. - `clear`: The runtime cache API does not support clearing by base. It expires the default `tags` configured on the driver; per-call clear options are not supported. - Metadata: Runtime cache does not expose metadata; `getMeta` is not implemented. - Persistence: This is not a persistent store; it’s intended for request-time caching inside Vercel Functions. ::note The unstorage driver does not hash keys by default. To reproduce that behavior when calling `getCache` from `@vercel/functions` directly, set `keyHashFunction: (key) => key`. :: ## Vercel Blob > Store data in a Vercel Blob Store. ::read-more{to="https://vercel.com/docs/storage/vercel-blob"} Learn more about Vercel Blob. :: ### Usage **Driver name:** `vercel-blob` To use, you will need to install [`@vercel/blob`](https://www.npmjs.com/package/@vercel/blob) dependency in your project: :pm-install{name="@vercel/blob"} #### Public access Public blobs are accessible via their URL without authentication. ```js import { createStorage } from "unstorage"; import vercelBlobDriver from "unstorage/drivers/vercel-blob"; const storage = createStorage({ driver: vercelBlobDriver({ access: "public", // token: "", // or set BLOB_READ_WRITE_TOKEN // base: "unstorage", // envPrefix: "BLOB", }), }); ``` #### Private access Private blobs require authentication to access. You need to create a private blob store on the Vercel dashboard before using this mode. ```js import { createStorage } from "unstorage"; import vercelBlobDriver from "unstorage/drivers/vercel-blob"; const storage = createStorage({ driver: vercelBlobDriver({ access: "private", // token: "", // or set BLOB_READ_WRITE_TOKEN // base: "unstorage", // envPrefix: "BLOB", }), }); ``` ### Options - `access`: Whether the blob should be publicly or privately accessible. Must be `"public"` or `"private"`. - `base`: Prefix to prepend to all keys. Can be used for namespacing. - `token`: REST API token for the Vercel Blob store. When omitted, it is read from `BLOB_READ_WRITE_TOKEN`. - `envPrefix`: Prefix to use for token environment variable name. Default is `BLOB` (env name = `BLOB_READ_WRITE_TOKEN`).