Indexed Database API 3.0

W3C Working Draft,

More details about this document
This version:
https://www.downtownmelody.com/_x/d3d3LnczLm9yZw/TR/2025/WD-IndexedDB-3-20250813/
Latest published version:
https://www.downtownmelody.com/_x/d3d3LnczLm9yZw/TR/IndexedDB/
Editor's Draft:
https://www.downtownmelody.com/_x/dzNjLmdpdGh1Yi5pbw/IndexedDB/
Previous Versions:
History:
https://www.downtownmelody.com/_x/d3d3LnczLm9yZw/standards/history/IndexedDB-3/
Test Suite:
https://www.downtownmelody.com/_x/Z2l0aHViLmNvbQ/web-platform-tests/wpt/tree/master/IndexedDB
Feedback:
GitHub
Editor:
(Microsoft)
Former Editors:
Ali Alabbas (Formerly of Microsoft)
Joshua Bell (Formerly of Google)

Abstract

This document defines APIs for a database of records holding simple values and hierarchical objects. Each record consists of a key and some value. Moreover, the database maintains indexes over records it stores. An application developer directly uses an API to locate records either by their key or by using an index. A query language can be layered on this API. An indexed database can be implemented using a persistent B-tree data structure.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C standards and drafts index.

This document was published by the Web Applications Working Group as a Working Draft using the Recommendation track. Publication as a Working Draft does not imply endorsement by W3C and its Members.

This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than a work in progress.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent that the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 03 November 2023 W3C Process Document.

This is the Third Edition of Indexed Database API. The First Edition, simply titled "Indexed Database API", became a W3C Recommendation on 8 January 2015. The Second Edition, titled "Indexed Database API 2.0", became a W3C Recommendation on 30 January 2018.

Indexed Database API 3.0 is intended to supersede Indexed Database API 2.0.

1. Introduction

User agents need to store large numbers of objects locally in order to satisfy off-line data requirements of Web applications. [WEBSTORAGE] is useful for storing pairs of keys and their corresponding values. However, it does not provide in-order retrieval of keys, efficient searching over values, or storage of duplicate values for a key.

This specification provides a concrete API to perform advanced key-value data management that is at the heart of most sophisticated query processors. It does so by using transactional databases to store keys and their corresponding values (one or more per key), and providing a means of traversing keys in a deterministic order. This is often implemented through the use of persistent B-tree data structures that are considered efficient for insertion and deletion as well as in-order traversal of very large numbers of data records.

The following example uses the API to access a "library" database. It has a "books" object store that holds books records stored by their "isbn" property as the primary key.

Book records have a "title" property. This example artificially requires that book titles are unique. The code enforces this by creating an index named "by_title" with the unique option set. This index is used to look up books by title, and will prevent adding books with non-unique titles.

Book records also have an "author" property, which is not required to be unique. The code creates another index named "by_author" to allow look-ups by this property.

The code first opens a connection to the database. The upgradeneeded event handler code creates the object store and indexes, if needed. The success event handler code saves the opened connection for use in later examples.

const request = indexedDB.open("library");
let db;

request.onupgradeneeded = function() {
  // The database did not previously exist, so create object stores and indexes.
  const db = request.result;
  const store = db.createObjectStore("books", {keyPath: "isbn"});
  const titleIndex = store.createIndex("by_title", "title", {unique: true});
  const authorIndex = store.createIndex("by_author", "author");

  // Populate with initial data.
  store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
  store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
  store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
};

request.onsuccess = function() {
  db = request.result;
};

The following example populates the database using a transaction.

const tx = db.transaction("books", "readwrite");
const store = tx.objectStore("books");

store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});

tx.oncomplete = function() {
  // All requests have succeeded and the transaction has committed.
};

The following example looks up a single book in the database by title using an index.

const tx = db.transaction("books", "readonly");
const store = tx.objectStore("books");
const index = store.index("by_title");

const request = index.get("Bedrock Nights");
request.onsuccess = function() {
  const matching = request.result;
  if (matching !== undefined) {
    // A match was found.
    report(matching.isbn, matching.title, matching.author);
  } else {
    // No match was found.
    report(null);
  }
};

The following example looks up all books in the database by author using an index and a cursor.

const tx = db.transaction("books", "readonly");
const store = tx.objectStore("books");
const index = store.index("by_author");

const request = index.openCursor(IDBKeyRange.only("Fred"));
request.onsuccess = function() {
  const cursor = request.result;
  if (cursor) {
    // Called for each matching record.
    report(cursor.value.isbn, cursor.value.title, cursor.value.author);
    cursor.continue();
  } else {
    // No more matching records.
    report(null);
  }
};

The following example shows one way to handle errors when a request fails.

const tx = db.transaction("books", "readwrite");
const store = tx.objectStore("books");
const request = store.put({title: "Water Buffaloes", author: "Slate", isbn: 987654});
request.onerror = function(event) {
  // The uniqueness constraint of the "by_title" index failed.
  report(request.error);
  // Could call event.preventDefault() to prevent the transaction from aborting.
};
tx.onabort = function() {
  // Otherwise the transaction will automatically abort due the failed request.
  report(tx.error);
};

The database connection can be closed when it is no longer needed.

db.close();

In the future, the database might have grown to contain other object stores and indexes. The following example shows one way to handle migrating from an older version.

const request = indexedDB.open("library", 3); // Request version 3.
let db;

request.onupgradeneeded = function(event) {
  const db = request.result;
  if (event.oldVersion < 1) {
    // Version 1 is the first version of the database.
    const store = db.createObjectStore("books", {keyPath: "isbn"});
    const titleIndex = store.createIndex("by_title", "title", {unique: true});
    const authorIndex = store.createIndex("by_author", "author");
  }
  if (event.oldVersion < 2) {
    // Version 2 introduces a new index of books by year.
    const bookStore = request.transaction.objectStore("books");
    const yearIndex = bookStore.createIndex("by_year", "year");
  }
  if (event.oldVersion < 3) {
    // Version 3 introduces a new object store for magazines with two indexes.
    const magazines = db.createObjectStore("magazines");
    const publisherIndex = magazines.createIndex("by_publisher", "publisher");
    const frequencyIndex = magazines.createIndex("by_frequency", "frequency");
  }
};

request.onsuccess = function() {
  db = request.result; // db.version will be 3.
};
A single database can be used by multiple clients (pages and workers) simultaneously — transactions ensure they don’t clash while reading and writing. If a new client wants to upgrade the database (via the upgradeneeded event), it cannot do so until all other clients close their connection to the current version of the database.

To avoid blocking a new client from upgrading, clients can listen for the versionchange event. This fires when another client is wanting to upgrade the database. To allow this to continue, react to the versionchange event by doing something that ultimately closes this client’s connection to the database.

One way of doing this is to reload the page:

db.onversionchange = function() {
  // First, save any unsaved data:
  saveUnsavedData().then(function() {
    // If the document isn't being actively used, it could be appropriate to reload
    // the page without the user's interaction.
    if (!document.hasFocus()) {
      location.reload();
      // Reloading will close the database, and also reload with the new JavaScript
      // and database definitions.
    } else {
      // If the document has focus, it can be too disruptive to reload the page.
      // Maybe ask the user to do it manually:
      displayMessage("Please reload this page for the latest version.");
    }
  });
};

function saveUnsavedData() {
  // How you do this depends on your app.
}

function displayMessage() {
  // Show a non-modal message to the user.
}

Another way is to call the connection’s close() method. However, you need to make sure your app is aware of this, as subsequent attempts to access the database will fail.

db.onversionchange = function() {
  saveUnsavedData().then(function() {
    db.close();
    stopUsingTheDatabase();
  });
};

function stopUsingTheDatabase() {
  // Put the app into a state where it no longer uses the database.
}

The new client (the one attempting the upgrade) can use the blocked event to detect if other clients are preventing the upgrade from happening. The blocked event fires if other clients still hold a connection to the database after their versionchange events have fired.

const request = indexedDB.open("library", 4); // Request version 4.
let blockedTimeout;

request.onblocked = function() {
  // Give the other clients time to save data asynchronously.
  blockedTimeout = setTimeout(function() {
    displayMessage("Upgrade blocked - Please close other tabs displaying this site.");
  }, 1000);
};

request.onupgradeneeded = function(event) {
  clearTimeout(blockedTimeout);
  hideMessage();
  // ...
};

function hideMessage() {
  // Hide a previously displayed message.
}

The user will only see the above message if another client fails to disconnect from the database. Ideally the user will never see this.

2. Constructs

A name is a string equivalent to a DOMString; that is, an arbitrary sequence of 16-bit code units of any length, including the empty string. Names are always compared as opaque sequences of 16-bit code units.

NOTE: As a result, name comparison is sensitive to variations in case as well as other minor variations such as normalization form, the inclusion or omission of controls, and other variations in Unicode text. [Charmod-Norm]

If an implementation uses a storage mechanism which does not support arbitrary strings, the implementation can use an escaping mechanism or something similar to map the provided name to a string that it can store.

To create a sorted name list from a list names, run these steps:

  1. Let sorted be names sorted in ascending order with the code unit less than algorithm.

  2. Return a new DOMStringList associated with sorted.

Details This matches the sort() method on an Array of String. This ordering compares the 16-bit code units in each string, producing a highly efficient, consistent, and deterministic sort order. The resulting list will not match any particular alphabet or lexicographical order, particularly for code points represented by a surrogate pair.

2.1. Database

Each storage key has an associated set of databases. A database has zero or more object stores which hold the data stored in the database.

A database has a name which identifies it within a specific storage key. The name is a name, and stays constant for the lifetime of the database.

A database has a version. When a database is first created, its version is 0 (zero).

NOTE: Each database has one version at a time; a database can’t exist in multiple versions at once. The only way to change the version is using an upgrade transaction.

A database has at most one associated upgrade transaction, which is either null or an upgrade transaction, and is initially null.

2.1.1. Database connection

Script does not interact with databases directly. Instead, script has indirect access via a connection. A connection object can be used to manipulate the objects of that database. It is also the only way to obtain a transaction for that database.

The act of opening a database creates a connection. There may be multiple connections to a given database at any given time.

A connection can only access databases associated with the storage key of the global scope from which the connection is opened.

NOTE: This is not affected by changes to the Document’s domain.

A connection has a version, which is set when the connection is created. It remains constant for the lifetime of the connection unless an upgrade is aborted, in which case it is set to the previous version of the database. Once the connection is closed the version does not change.

Each connection has a close pending flag which is initially false.

When a connection is initially created it is in an opened state. The connection can be closed through several means. If the execution context where the connection was created is destroyed (for example due to the user navigating away from that page), the connection is closed. The connection can also be closed explicitly using the steps to close a database connection. When the connection is closed its close pending flag is always set to true if it hasn’t already been.

A connection may be closed by a user agent in exceptional circumstances, for example due to loss of access to the file system, a permission change, or clearing of the storage key’s storage. If this occurs the user agent must run close a database connection with the connection and with the forced flag set to true.

A connection has an object store set, which is initialized to the set of object stores in the associated database when the connection is created. The contents of the set will remain constant except when an upgrade transaction is live.

A connection’s get the parent algorithm returns null.

An event with type versionchange will be fired at an open connection if an attempt is made to upgrade or delete the database. This gives the connection the opportunity to close to allow the upgrade or delete to proceed.

An event with type close will be fired at a connection if the connection is closed abnormally.

2.2. Object store

An object store is the primary storage mechanism for storing data in a database.

Each database has a set of object stores. The set of object stores can be changed, but only using an upgrade transaction, i.e. in response to an upgradeneeded event. When a new database is created it doesn’t contain any object stores.

An object store has a list of records which hold the data stored in the object store. Each record consists of a key and a value. The list is sorted according to key in ascending order. There can never be multiple records in a given object store with the same key.

An object store has a name, which is a name. At any one time, the name is unique within the database to which it belongs.

An object store optionally has a key path. If the object store has a key path it is said to use in-line keys. Otherwise it is said to use out-of-line keys.

An object store optionally has a key generator.

An object store can derive a key for a record from one of three sources:

  1. A key generator. A key generator generates a monotonically increasing numbers every time a key is needed.

  2. Keys can be derived via a key path.

  3. Keys can also be explicitly specified when a value is stored in the object store.

2.2.1. Object store handle

Script does not interact with object stores directly. Instead, within a transaction, script has indirect access via an object store handle.

An object store handle has an associated object store and an associated transaction. Multiple handles may be associated with the same object store in different transactions, but there must be only one object store handle associated with a particular object store within a transaction.

An object store handle has an index set, which is initialized to the set of indexes that reference the associated object store when the object store handle is created. The contents of the set will remain constant except when an upgrade transaction is live.

An object store handle has a name, which is initialized to the name of the associated object store when the object store handle is created. The name will remain constant except when an upgrade transaction is live.

2.3. Values

Each record is associated with a value. User agents must support any serializable object. This includes simple types such as String primitive values and Date objects as well as Object and Array instances, File objects, Blob objects, ImageData objects, and so on. Record values are stored and retrieved by value rather than by reference; later changes to a value have no effect on the record stored in the database.

Record values are Records output by the StructuredSerializeForStorage operation.

2.4. Keys

In order to efficiently retrieve records stored in an indexed database, each record is organized according to its key.

A key has an associated type which is one of: number, date, string, binary, or array.

A key also has an associated value, which will be either: an unrestricted double if type is number or date, a DOMString if type is string, a byte sequence if type is binary, or a list of other keys if type is array.

An ECMAScript [ECMA-262] value can be converted to a key by following the steps to convert a value to a key.

NOTE: The following ECMAScript types are valid keys:

Attempting to convert other ECMAScript values to a key will fail.

An array key is a key with type array. The subkeys of an array key are the items of the array key’s value.

To compare two keys a and b, run these steps:

  1. Let ta be the type of a.

  2. Let tb be the type of b.

  3. If ta does not equal tb, then run these steps:

    1. If ta is array, then return 1.

    2. If tb is array, then return -1.

    3. If ta is binary, then return 1.

    4. If tb is binary, then return -1.

    5. If ta is string, then return 1.

    6. If tb is string, then return -1.

    7. If ta is date, then return 1.

    8. Assert: tb is date.

    9. Return -1.

  4. Let va be the value of a.

  5. Let vb be the value of b.

  6. Switch on ta:

    number
    date
    1. If va is greater than vb, then return 1.

    2. If va is less than vb, then return -1.

    3. Return 0.

    string
    1. If va is code unit less than vb, then return -1.

    2. If vb is code unit less than va, then return 1.

    3. Return 0.