IndexedDB API Reference

IDBDatabase, IDBObjectStore, IDBIndex and IDBCursor methods with transaction modes.

Searchable IndexedDB API reference covering IDBFactory, IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor and IDBTransaction methods, plus readonly, readwrite and versionchange transaction modes. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What are the three IndexedDB transaction modes?

readonly allows concurrent reads with no writes and is the default. readwrite permits reads and writes but serialises against other writers on the same store. versionchange is created automatically during upgradeneeded and is the only mode that can add or remove stores and indexes.

A map of the IndexedDB object graph

IndexedDB is a transactional, asynchronous, key-value store built into the browser for large structured data and offline apps. Its API spans several interfaces — a factory, the database, object stores, indexes, cursors and transactions — each with its own methods. This searchable reference lists the core members with their signatures so you can find the right call quickly.

How it works

You open a database, declare stores and indexes during an upgrade, then read and write inside transactions:

const req = indexedDB.open("app", 2);
req.onupgradeneeded = (e) => {
  const db = e.target.result;
  const store = db.createObjectStore("notes", { keyPath: "id" });
  store.createIndex("by_tag", "tag", { unique: false });
};
req.onsuccess = (e) => {
  const db = e.target.result;
  const tx = db.transaction("notes", "readwrite");
  tx.objectStore("notes").put({ id: 1, tag: "todo", body: "ship it" });
};

Every operation returns an IDBRequest whose onsuccess/onerror fire asynchronously. Transactions auto-commit when control returns to the event loop with no pending requests. Schema changes are confined to the special versionchange transaction during upgradeneeded.

Tips and notes

  • Use keyPath for in-line keys (the key lives in the value) or autoIncrement for generated keys.
  • Cursors iterate in key order; pass prev for descending or nextunique to skip duplicate index keys.
  • getAll() is faster than a cursor when you simply need every matching value as an array.
  • A thrown error or tx.abort() rolls back every change made in that transaction — IndexedDB is fully atomic per transaction.

Common patterns and pitfalls

Reading inside the same transaction you wrote to

Transactions auto-commit once the JavaScript event loop returns with no pending requests. This means you cannot open a readwrite transaction, do a put, return to the event loop, and then re-use the same transaction handle for a later read. The transaction will already be committed (or aborted). Chain all related reads and writes within a single transaction’s request callbacks.

Multiple object stores in one transaction

A single transaction can span multiple object stores — pass an array of names to db.transaction(['store1', 'store2'], 'readwrite'). This is important when you need to keep two stores consistent, since only a single transaction provides the atomicity guarantee.

Index range queries

Use IDBKeyRange with openCursor() or getAll() on an index to retrieve a bounded set of records without loading the whole store:

const idx = tx.objectStore('notes').index('by_tag');
const range = IDBKeyRange.only('todo');
idx.getAll(range).onsuccess = (e) => console.log(e.target.result);

This is substantially faster than cursoring the whole store and filtering in JavaScript.

Version migrations

Migrations run inside onupgradeneeded. The event fires every time the version number increases, and event.oldVersion tells you which version the database was on before. Use a sequential upgrade pattern:

req.onupgradeneeded = (e) => {
  const db = e.target.result;
  if (e.oldVersion < 1) {
    db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
  }
  if (e.oldVersion < 2) {
    db.transaction.objectStore('notes').createIndex('by_tag', 'tag');
  }
};

This lets users who missed a version catch up through all migrations in order.

Error handling

Every IDBRequest has an onerror handler, and every transaction has onerror and onabort. If you do not handle errors, they bubble to the database and then to window.onerror. A common defensive pattern is to attach an onerror to the transaction itself to catch any uncaught request error and log or report it.

Storage limits

IndexedDB is subject to the browser’s storage quota, which varies by origin and can be as low as a few hundred megabytes on mobile devices. For very large datasets, check navigator.storage.estimate() before writing, and handle the QuotaExceededError DOMException gracefully.