# Dive into IndexedDB

> Learn IndexedDB with the idb promise wrapper: create a database, store and query structured data, use transactions, and upgrade schemas.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-02-28 | Updated: 2026-07-18 | Topics: [Web Platform](https://flaviocopes.com/tags/platform/) | Canonical: https://flaviocopes.com/indexeddb/

IndexedDB is the browser's asynchronous database for structured data. It can store JavaScript objects, arrays, dates, files, blobs, and binary data.

Use IndexedDB when browser storage must hold more or richer data than a few string values. Good examples include offline application data, cached documents, drafts, and local indexes.

For a small preference such as a color theme, `localStorage` is simpler.

The native IndexedDB API is event-based. In this guide I use the small [`idb`](https://github.com/jakearchibald/idb) library, which adds promises and convenient shortcuts while preserving IndexedDB's concepts.

## Install idb

Install the package:

```bash
npm install idb
```

Import the APIs you need:

```js
import { deleteDB, openDB } from 'idb'
```

The library works in browsers. IndexedDB is not available during server-side rendering, so open the database only in browser code.

## Create a database and object store

Open a database with a name and version:

```js
import { openDB } from 'idb'

const db = await openDB('notes-app', 1, {
  upgrade(db) {
    db.createObjectStore('notes', {
      keyPath: 'id',
    })
  },
})
```

An **object store** is similar to a table. This example uses each note's `id` property as its key.

The `upgrade()` callback runs when the database is first created and whenever you open it with a higher version number. Schema changes must happen there.

The callback receives a native upgrade transaction. Keep it focused on database changes; do not wait for network requests inside it.

## Add or replace data

Use `add()` when a key must not already exist:

```js
await db.add('notes', {
  id: 'note-1',
  title: 'Shopping list',
  body: 'Milk and bread',
  updatedAt: new Date(),
})
```

If `note-1` already exists, `add()` rejects with a constraint error.

Use `put()` when you want to insert a new value or replace the value at an existing key:

```js
await db.put('notes', {
  id: 'note-1',
  title: 'Shopping list',
  body: 'Milk, bread, and coffee',
  updatedAt: new Date(),
})
```

IndexedDB stores values using the structured clone algorithm. Functions and DOM elements cannot be stored.

## Read data

Get one value by key:

```js
const note = await db.get('notes', 'note-1')

if (note) {
  console.log(note.title)
}
```

`get()` returns `undefined` when the key does not exist.

Get every value in the store:

```js
const notes = await db.getAll('notes')
```

For a large store, do not load everything blindly. Add an index and query only the records the interface needs.

## Add an index

An index lets you query by a property other than the primary key.

Indexes are part of the schema, so create one during an upgrade:

```js
const db = await openDB('notes-app', 2, {
  upgrade(db, oldVersion, newVersion, transaction) {
    if (oldVersion < 1) {
      db.createObjectStore('notes', {
        keyPath: 'id',
      })
    }

    if (oldVersion < 2) {
      const store = transaction.objectStore('notes')
      store.createIndex('by-updated-at', 'updatedAt')
    }
  },
})
```

Then query through the index:

```js
const notes = await db.getAllFromIndex('notes', 'by-updated-at')
```

Version checks allow a user to upgrade directly from any older schema instead of assuming everyone has the previous version.

## Use a transaction for related operations

The shortcut methods such as `db.put()` create a transaction for one operation.

When several changes must succeed or fail together, create one read-write transaction:

```js
const tx = db.transaction('notes', 'readwrite')
const store = tx.objectStore('notes')

await store.put({
  id: 'note-2',
  title: 'Ideas',
  body: 'Build an offline notes app',
  updatedAt: new Date(),
})

await store.delete('note-1')
await tx.done
```

`tx.done` resolves when the entire transaction completes and rejects if it aborts.

Await requests as you issue them, then await `tx.done`. Do not perform unrelated async work, such as a fetch request, in the middle of a transaction. IndexedDB transactions can become inactive when control returns to the event loop without a pending database request.

Transactions are atomic: if one request fails and the transaction aborts, none of its changes are committed.

## Delete records

Delete one record by key:

```js
await db.delete('notes', 'note-2')
```

Clear every record while keeping the object store:

```js
await db.clear('notes')
```

Delete the entire database:

```js
db.close()
await deleteDB('notes-app')
```

Other open tabs can block a version upgrade or database deletion. Applications that stay open for a long time should close an old connection when a newer version is requested:

```js
const db = await openDB('notes-app', 2, {
  upgrade(db, oldVersion, newVersion, transaction) {
    // Apply versioned schema changes here.
  },
  blocking() {
    db.close()
  },
})
```

The page can then tell the user to refresh.

## Check whether a store exists

`objectStoreNames` is a list-like property, not a function:

```js
if (db.objectStoreNames.contains('notes')) {
  console.log('The notes store exists')
}
```

Create and delete object stores only inside the version-change transaction passed to `upgrade()`.

## Storage limits and persistence

IndexedDB capacity and eviction behavior vary by browser, device, available disk space, and browsing mode. Private browsing storage is generally temporary.

Do not assume data will remain forever. Important user data needs a backup or server synchronization strategy.

The Storage API can report estimated usage and quota:

```js
const estimate = await navigator.storage.estimate()

console.log(estimate.usage)
console.log(estimate.quota)
```

An application can request persistent storage with `navigator.storage.persist()`, but the browser decides whether to grant it.

For more detail, see MDN's [IndexedDB API overview](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), [Using IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB), and [storage quota and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria).
