# idb: using IndexedDB with promises instead of IDBRequest events

> idb is a 1.19kB brotli'd library by Jake Archibald that replaces IndexedDB's IDBRequest event model with promises, without changing the underlying transaction lifetime rules. Four top-level functions cover opening, deleting, wrapping and unwrapping databases, and every method that normally returns IDBRequest now returns a promise for its result.

**jakearchibald/idb** — IndexedDB, but with promises

- Repository: https://github.com/jakearchibald/idb
- Website: https://www.npmjs.com/package/idb
- Stars: 7,422 · Forks: 384
- Language: TypeScript
- License: ISC
- Published: 2026-10-11 · Updated: 2026-10-11 · Language: en
- Canonical page: https://hysenlabs.com/projects/jakearchibald-idb

## IDBRequest results become promises; the transaction auto-commit window stays the same

IndexedDB's native API expresses every result as an IDBRequest object with onsuccess and onerror handlers. idb replaces that with promises for those results, which means code that used to read like a chain of event listeners can instead use async/await.

What the library does not change is how the IndexedDB transaction model works. An IDB transaction auto-closes once it has nothing left to do after microtasks have been processed. That behavior is in the browser, not in the library, and idb does not extend or delay it. Adding promises to the method calls makes them await-able, but it does not give a transaction more time to stay open.

4 top-level exports handle the database-level operations: openDB, deleteDB, wrap and unwrap. Everything else follows the existing IndexedDB API shape, with the IDBRequest return values swapped for promise-based ones.

At 1.19kB brotli'd, the library adds almost nothing to a bundle. Package.json sets the type field to module, publishes the ES module build at build/index.js, and includes a CommonJS fallback at build/index.cjs for environments that need it.

## npm install idb, a CDN module path and a UMD script are all available

Installing via npm is the documented path for bundler-based projects:

```sh
npm install idb
```

After that, 4 exports cover the main use cases:

```js
import { openDB, deleteDB, wrap, unwrap } from 'idb';

async function doDatabaseStuff() {
  const db = await openDB(…);
}
```

For pages that import modules directly without a build step, jsdelivr hosts an ES module build at a version-pinned URL:

```html
<script type="module">
  import { openDB, deleteDB, wrap, unwrap } from 'https://cdn.jsdelivr.net/npm/idb@8/+esm';

  async function doDatabaseStuff() {
    const db = await openDB(…);
  }
</script>
```

For pages that need a plain script tag without module syntax, a UMD build is also available:

```html
<script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script>
<script>
  async function doDatabaseStuff() {
    const db = await idb.openDB(…);
  }
</script>
```

With the UMD build, all exports are available under a global named idb. The README documents 3 distinct install paths: bundlers, direct ES module imports and legacy script includes.

## openDB takes four callbacks that correspond to four IDB lifecycle events

Opening a database with openDB accepts a name, an optional version, and an options object with 4 callbacks:

```js
const db = await openDB(name, version, {
  upgrade(db, oldVersion, newVersion, transaction, event) {
    // …
  },
  blocked(currentVersion, blockedVersion, event) {
    // …
  },
  blocking(currentVersion, blockedVersion, event) {
    // …
  },
  terminated() {
    // …
  },
});
```

upgrade fires when this version of the database has never been opened before, and it receives 5 parameters: db, oldVersion, newVersion, transaction and event. That enhanced transaction is useful when a migration needs to read data from one store while writing to another.

blocked fires when an older version is still open on the same origin and delivers 3 fields: currentVersion, blockedVersion and event. blocking fires when this connection is preventing a newer version from opening. terminated fires when the browser abnormally closes the connection, but not on regular closures such as calling db.close(). That distinction matters for reconnect logic: a clean close does not trigger terminated, so code that relies on it to detect connection loss will not see clean closes at all.

## Awaiting a fetch() inside a transaction closes the transaction before the write runs

The transaction lifetime rule is the one behavior difference between idb and the raw API that causes runtime errors without any warning. An IDB transaction auto-closes when the browser has processed all pending microtasks and there is nothing left in the queue. A write that goes into the queue after an awaited network call arrives too late.

A working example keeps all awaited operations inside the database:

```js
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = (await store.get('counter')) || 0;
await store.put(val + 1, 'counter');
await tx.done;
```

A broken example awaits a network call between the read and the write:

```js
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = (await store.get('counter')) || 0;
// This is where things go wrong:
const newVal = await fetch('/increment?val=' + val);
// And this throws an error:
await store.put(newVal, 'counter');
await tx.done;
```

In this case, the transaction closes while the browser is fetching, so store.put fails. The README calls this the TL;DR for the transaction lifetime section. Fetching the remote value before opening the transaction, and passing the result in, is the correct pattern.

## store.put may throw rather than reject when the library cannot predict the return type

One design consequence of wrapping IDBRequest objects with promises is that the library does not know in advance which methods will return promises. Its approach is to convert whatever comes back, but methods like store.put may throw a synchronous exception rather than returning a rejected promise.

Inside an async function, the observable difference between a throw and a rejection is zero, because both are caught by a surrounding try/catch or bubble up as an unhandled promise rejection. Outside an async function, the distinction matters: a thrown error in a synchronous context does not become a promise rejection.

This is a design boundary worth knowing before adding error handling. Code that wraps idb operations in .catch() may miss thrown errors if those operations are not inside an async function.

## unwrap and wrap let you pass IDB objects between idb-enhanced code and plain IDB libraries

2 functions cover interoperability with code that works with raw IDB objects. unwrap takes an enhanced IndexedDB object and returns the plain unmodified version; promises are also converted back into IDBRequest objects. wrap takes a plain IDB object and returns a version enhanced by this library.

When to use them: unwrap is for cases where you want to pass an idb-enhanced handle to code that expects raw IndexedDB, or when you need the IDBRequest interface for some specific reason. wrap is for cases where a third-party library gives you a raw IDBDatabase and you want the promise-based methods instead.

Neither function does anything to the underlying database. Both are adapters at the JavaScript object level, so switching between them does not change what is stored or how the browser handles the connection.

## Chrome, Firefox, Safari and Edge are the documented targets; IE is excluded

Browser support in the README is defined by engine rather than a specific version list: Chrome, Firefox, Safari and Edge, along with other browsers that use those engines. IE is explicitly excluded. Since idb wraps the browser's built-in IndexedDB, any browser that ships IndexedDB is a candidate, but the library makes no promise about behavior in non-listed environments.

Node.js compatibility is not mentioned in the README. IndexedDB is a browser API, and using it in server-side code would require a separate IndexedDB implementation not shipped in this package.

The package.json prepack script runs the build before packaging, so installing from npm always gets the compiled output in the build/ directory. The module field points to build/index.js for ES module-aware bundlers, and main points to build/index.cjs for CommonJS environments. TypeScript types are at build/index.d.ts. The library ships in ISC-licensed form, and CHANGELOG.md at the repository root tracks potentially breaking changes between versions.

## Conclusion

idb is worth using when the event-based IndexedDB API makes code hard to follow and your application runs only in modern browsers. The promise layer removes the IDBRequest overhead but does not change the transaction timing model, so any code that worked in plain IndexedDB needs to be checked against one rule: do not await anything outside the database between transaction start and the final write. Skip it when IE support is a requirement or when your codebase already wraps IndexedDB with another library. Before adopting, read CHANGELOG.md in the repository, since the README notes it tracks potentially breaking changes.

## FAQ

### What is idb?

idb is a small JavaScript library that wraps the browser's IndexedDB API so that operations return promises instead of IDBRequest objects. It weighs 1.19kB brotli'd and is ISC-licensed.

### How do I install idb?

Run npm install idb in your project, then import openDB, deleteDB, wrap and unwrap from 'idb'. For use without a bundler, the library is also available as an ES module or a UMD script from cdn.jsdelivr.net/npm/idb@8.

### Does idb support Internet Explorer?

No. The README states that IE is not supported. Browser support covers Chrome, Firefox, Safari and Edge, along with other browsers that use those engines.

### Why does my idb transaction fail when I use fetch() inside it?

An IDB transaction auto-closes once the browser has processed all pending microtasks and the queue is empty. Awaiting a network call like fetch() inside the transaction lets it close before any subsequent write, causing that write to fail. Fetch the data you need before opening the transaction, then pass it in.

### Where are idb's release notes?

The repository has no GitHub releases. The README points to CHANGELOG.md at the repository root as the location for potentially breaking changes between versions.

## Sources

- [Issues](https://github.com/jakearchibald/idb/issues)
- [jakearchibald/idb on GitHub](https://github.com/jakearchibald/idb)
- [License: ISC](https://github.com/jakearchibald/idb/blob/main/LICENSE)
- [Project website](https://www.npmjs.com/package/idb)
- [README](https://github.com/jakearchibald/idb/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jakearchibald-idb
