HTML IndexedDB

Beginner
10 min read
Updated: Nov 2026
4 CRUD tutorials
Live preview

Definition and Usage

IndexedDB is a low-level browser API for storing large amounts of structured data on the client—including files and blobs. Unlike localStorage, it stores JavaScript objects, supports indexes, and runs work inside transactions.

Use this hub to learn the core ideas, try a live demo, then open the focused CRUD tutorials: Insert, Retrieve, Update, and Delete.

Baseline · Widely available.

Not deprecated. IndexedDB has been stable across browsers since about July 2015. Also available in Web Workers. Same-origin only—one site cannot read another site’s database.

vs Web SQL.

Web SQL is deprecated. Prefer IndexedDB for new offline storage. Think object stores (JS objects) instead of SQL tables.

Key Concepts

TermMeaning
DatabaseTop-level container opened with indexedDB.open(name, version)
Object storeCollection of records (like a table of JS objects)
Key / keyPathPrimary identifier for a record (e.g. id)
IndexSecondary lookup path (e.g. search by email)
TransactionAtomic unit of work: readonly or readwrite
CursorIterator that walks records with continue()
UpgradeSchema changes only in onupgradeneeded when the version increases

Syntax

syntax.js
indexedDB.open( name, version?)

Then create stores in onupgradeneeded, and run reads/writes inside db.transaction(storeNames, mode).

TaskPattern
Create storedb.createObjectStore(name, { keyPath }) in upgrade
Insertstore.add(obj) / store.put(obj)
Readstore.get(key) / store.getAll()
Updatestore.put(obj) (full replace)
Deletestore.delete(key) / store.clear()

How It Works

A typical first IndexedDB program follows these steps:

  1. Feature-detect with window.indexedDB.
  2. Open with indexedDB.open('myDb', 1).
  3. Create the store in onupgradeneeded if needed.
  4. On success, keep the IDBDatabase handle.
  5. Start a transaction, get the store, then add / get / put / delete.
JS
const request = indexedDB.open('myDatabase', 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  if (!db.objectStoreNames.contains('contacts')) {
    db.createObjectStore('contacts', { keyPath: 'id' });
  }
};

request.onsuccess = (event) => {
  const db = event.target.result;
  console.log('Database ready');
};

CRUD Tutorials

Each page is lean and focused: definition, syntax, live preview, examples with How It Works, and FAQs.

Live Preview

Open a demo database, seed a contact, read it, then clear the store.

Private demo database in this browser. Safe to click repeatedly.

Status

Click Open DB to start.

Examples

Short starter snippets. Use Try It Yourself for a live editor, then dive into the CRUD pages.

Example 1 — Open a Database and Create a Store

Bump the version and create the store only inside onupgradeneeded.

JS
const request = indexedDB.open('demoDb', 1);

request.onupgradeneeded = function (event) {
  const db = event.target.result;
  db.createObjectStore('contacts', { keyPath: 'id' });
};

request.onsuccess = function (event) {
  console.log('Ready:', event.target.result.name);
};

request.onerror = function () {
  console.error(request.error);
};
Try It Yourself

How It Works

  1. Call open(name, version) to connect or create the database.
  2. Handle onupgradeneeded to create object stores and indexes.
  3. Use onsuccess to keep the database connection.
  4. Handle onerror for open failures (private mode, quota, etc.).

Example 2 — Insert a Record

Writes need a readwrite transaction.

JS
const tx = db.transaction(['contacts'], 'readwrite');
const req = tx.objectStore('contacts').add({
  id: 1,
  name: 'Alex Rivera',
  email: '[email protected]'
});

req.onsuccess = function () {
  console.log('Inserted key:', req.result);
};
Try It Yourself

How It Works

  1. Start readwrite on the store name.
  2. Call add() for insert-only (fails on duplicate keys).
  3. Use put() when insert-or-replace is fine.
  4. See the Insert tutorial for ConstraintError and autoIncrement.

Example 3 — Read a Record

Reads usually use a readonly transaction.

JS
const tx = db.transaction(['contacts'], 'readonly');
const req = tx.objectStore('contacts').get(1);

req.onsuccess = function () {
  const contact = req.result;
  if (!contact) {
    console.log('Not found');
    return;
  }
  console.log(contact.name);
};
Try It Yourself

How It Works

  1. Open a readonly transaction (default mode).
  2. Call get(key) for one primary key.
  3. Check for undefined—missing keys do not throw.
  4. See Retrieve for getAll(), cursors, and indexes.

Example 4 — Mini HTML Demo

Open, seed, and read from buttons on one page.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>IndexedDB Demo</title>
</head>
<body>
  <h1>IndexedDB Demo</h1>
  <button type="button" id="seedBtn" disabled>Seed</button>
  <button type="button" id="getBtn" disabled>Get id 1</button>
  <p id="status">Opening…</p>

  <script>
    let db;
    const openReq = indexedDB.open('hubDemoDb', 1);

    openReq.onupgradeneeded = function (event) {
      event.target.result.createObjectStore('contacts', { keyPath: 'id' });
    };

    openReq.onsuccess = function (event) {
      db = event.target.result;
      document.getElementById('status').textContent = 'Ready.';
      document.getElementById('seedBtn').disabled = false;
      document.getElementById('getBtn').disabled = false;
    };

    document.getElementById('seedBtn').addEventListener('click', function () {
      const tx = db.transaction(['contacts'], 'readwrite');
      tx.objectStore('contacts').put({
        id: 1,
        name: 'Alex Rivera'
      });
      tx.oncomplete = function () {
        document.getElementById('status').textContent = 'Seeded id 1.';
      };
    });

    document.getElementById('getBtn').addEventListener('click', function () {
      const tx = db.transaction(['contacts'], 'readonly');
      const req = tx.objectStore('contacts').get(1);
      req.onsuccess = function () {
        const c = req.result;
        document.getElementById('status').textContent = c
          ? c.name
          : 'Not found';
      };
    });
  </script>
</body>
</html>
Try It Yourself

How It Works

  1. Open and create the store on first visit.
  2. Seed with put() so repeat clicks stay safe.
  3. Read with get(1) and show the name.
  4. Continue with Update and Delete tutorials for the rest of CRUD.

Edge Cases & Pitfalls

Most beginner bugs come from schema timing, wrong transaction mode, or treating IndexedDB like SQL.

schema

Stores only in onupgradeneeded

You cannot call createObjectStore() during a normal transaction. Bump the version first.

mode

Writes need readwrite

add, put, delete, and clear fail in a readonly transaction.

async

Everything is asynchronous

Results arrive in onsuccess (or promises via wrappers). Do not treat requests as synchronous returns.

origin

Same-origin only

Data is scoped to the site origin. Other domains cannot read it.

tabs

Version upgrades block other tabs

Listen for db.onversionchange and close the connection so another tab can finish upgrading.

Browser Support

IndexedDB is a standard browser Web Storage API—not a Node.js, Deno, or Bun API.

✓ Baseline · Widely available

IndexedDB API

Widely available since about July 2015. Chrome, Firefox, Safari, Edge, and Opera support open(), object stores, transactions, cursors, and indexes.

96% Global support (approx.)
Google Chrome 24+
Full support
Mozilla Firefox 16+
Full support
Apple Safari 10+
Full support
Microsoft Edge 12+
Full support
Opera 15+
Full support
Samsung Internet 1.5+
Full support
Internet Explorer 10+
Partial / older
IndexedDB Widely supported

Bottom line: Safe for production web apps. Feature-detect with window.indexedDB before opening a database.

Key Takeaways

  • Role: IndexedDB stores structured objects for offline / large client data.
  • Schema: create stores and indexes only in onupgradeneeded.
  • CRUD: use transactions + add/get/put/delete on object stores.
  • Path: learn Insert → Retrieve → Update → Delete next.

One line: open a DB, create stores on upgrade, then CRUD inside transactions.

Frequently Asked Questions

IndexedDB is a browser API for storing large amounts of structured data on the client. You open a database with indexedDB.open(), save JavaScript objects in object stores, and read or change them inside transactions. It works asynchronously so the page stays responsive.
localStorage stores simple key-value strings with a small size limit. IndexedDB stores objects, blobs, and files, supports indexes for fast lookups, and handles much larger datasets. Use localStorage for a few settings; use IndexedDB for offline apps and rich data.
The onupgradeneeded event runs when you open a database with a higher version number than before. Inside it you create or delete object stores and indexes. Normal add/get/delete operations happen later in readwrite or readonly transactions—not during upgrade.
add() inserts a new record and fails if the key already exists. put() inserts or replaces the record with the same key (upsert). Use add() when duplicates must be rejected; use put() for updates and idempotent saves.
IndexedDB groups work into transactions for consistency. A readwrite transaction commits all writes together or rolls back on error. You request a transaction on one or more object store names, then call methods on transaction.objectStore('name').
Open the Insert tutorial next to add records, then Retrieve, Update, and Delete. Each page has live demos and Try It editors focused on one operation.

Next: IndexedDB Insert

Add your first records with add() and put() inside a readwrite transaction.

Insert tutorial →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

12 people found this page helpful