idb-keyval
idb-keyval is a tiny promise-based key/value layer over the browser's IndexedDB database. It gives you get, set, delete, batch, update, and enumeration helpers without making you manage requests, transactions, object stores, or event handlers directly. Values can be anything supported by structured cloning, including objects, arrays, dates, and blobs. It is deliberately not a general database wrapper: there are no indexes, queries, migrations, or relationships.
Install it for small, browser-only persistence where a key/value API is the whole requirement. Once you need querying, migrations, cross-runtime storage, or synchronization, start with a fuller database layer instead of stretching this one.
Use it if
- You need persistent browser storage for a modest collection of values and localStorage is too limited or synchronous
- You want IndexedDB durability and structured-clone support without learning its request and transaction APIs
- You need atomic batch writes or queued updates for counters and other read-modify-write operations
- You care about a sub-kilobyte gzip cost and want imports that tree-shake down to only the helpers you use
- You need indexes, range queries, cursors, schema upgrades, or multiple related stores: the README calls this only a key/value store and points those users to idb
- You need storage in Node.js, server rendering, or a worker runtime without IndexedDB: this package is a browser API wrapper and provides no persistence fallback
- You must support browsers with missing or unreliable IndexedDB implementations: the README recommends localForage for that wider compatibility, while IE also needs the compat build and a Promise polyfill
- You need cross-device sync, encryption, conflict resolution, quotas you control, or guaranteed eviction behavior: idb-keyval only exposes the local browser database underneath it
- You need to distinguish a missing key from a deliberately stored undefined value: get resolves to undefined when no record exists, so those cases share the same result
Setup reality
Installation is only `npm install idb-keyval`; there are no dependencies, peer dependencies, credentials, native builds, or config files. Modern bundlers choose the ESM or CommonJS export, and TypeScript declarations ship in the package. The catch is the platform contract. IndexedDB must exist, so server-side rendering code cannot touch the store during render and Node-only tests need a browser or an IndexedDB shim. Every method is asynchronous and can reject for quota, permission, private-mode, serialization, or transaction failures, so writes should be awaited and failures should not disappear into fire-and-forget calls. Values must be structured-clonable; functions, DOM nodes, and other non-cloneable objects will fail. The default database is named `keyval-store` and its object store is `keyval`; call `createStore` when applications on the same origin need isolation, and note that one database cannot hold two custom stores. `get` returning undefined means either no record or an explicitly stored undefined, which can make cache logic ambiguous. For IE 10 or 11 the README requires `idb-keyval/compat` plus a Promise polyfill, and its notes call out older Edge and IE limitations around null and array keys. Finally, a `get` followed by `set` is not an atomic update. Use `update` or the batch helpers when concurrent callers can touch the same key.
Patterns
Store a structured-clone valuestore-value
import { set } from 'idb-keyval';
await set('profile', { name: 'Ada', seenAt: new Date() });Await the promise so quota, permission, and serialization failures reach your error handling.
Read a valueread-value
import { get } from 'idb-keyval';
const profile = await get('profile');
if (profile === undefined) {
console.log('not cached');
}A missing key resolves to undefined; storing undefined makes absence indistinguishable from that stored value.
Delete one keydelete-value
import { del } from 'idb-keyval';
await del('profile');Deletion is asynchronous even when the key does not exist.
Write several values atomicallywrite-many
import { setMany } from 'idb-keyval';
await setMany([
['theme', 'dark'],
['page-size', 50],
]);setMany uses one transaction; if one pair cannot be added, none of the pairs are committed.
Read several keys in one transactionread-many
import { getMany } from 'idb-keyval';
const [theme, pageSize] = await getMany(['theme', 'page-size']);The result order matches the key order, and missing records appear as undefined entries.
Increment without a lost updateupdate-atomically
import { update } from 'idb-keyval';
await update('launch-count', (value = 0) => value + 1);Use update for read-modify-write logic; separate get and set calls can race and overwrite each other.
Delete several keys togetherdelete-many
import { delMany } from 'idb-keyval';
await delMany(['draft', 'draft-saved-at']);delMany is faster than scheduling separate del calls because it shares one transaction.
Clear the active storeclear-store
import { clear } from 'idb-keyval';
await clear();With the default store this removes every idb-keyval record for the origin, so do not use it as a one-user logout unless all values are user-specific.
List keys and valueslist-entries
import { entries } from 'idb-keyval';
for (const [key, value] of await entries()) {
console.log(key, value);
}This enumerates the whole store; it is not an indexed query and can be expensive for large datasets.
List all keyslist-keys
import { keys } from 'idb-keyval';
const cacheKeys = await keys();Keys may be strings, numbers, or dates; older IE does not support IndexedDB array keys.
Isolate data in a named storecustom-store
import { createStore, get, set } from 'idb-keyval';
const drafts = createStore('editor-db', 'drafts');
await set('post-42', { title: 'Draft' }, drafts);
const draft = await get('post-42', drafts);Pass the same store handle as the final argument to every helper. `createStore` cannot create two stores inside one database, so use a separate database name per store.
Load the compatibility buildlegacy-browser-build
import 'es6-promise/auto';
import { get, set } from 'idb-keyval/compat';
await set('answer', 42);
console.log(await get('answer'));The README requires both the compat entry point and a Promise polyfill for IE 10 and 11; this does not fix every old IndexedDB quirk.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| idb | npm | You need real IndexedDB databases with indexes, cursors, transactions, and versioned upgrades |
| dexie | npm | You want a richer database layer with declarative schemas, queries, hooks, and live-query tooling |
| localforage | npm | You need fallbacks for older browsers where IndexedDB is absent or unreliable |