HTML IndexedDB Insert

Beginner
⏱️ 12 min read
📚 Updated: Jul 2026
🎯 5 Examples + 3 Try It
add() / put() / transaction

Introduction

IndexedDB is a low-level browser API for storing large amounts of structured data on the client. Unlike localStorage, it supports indexes, transactions, and complex JavaScript objects—including files and blobs. It is the modern replacement for the deprecated Web SQL API.

This tutorial focuses specifically on inserting data into IndexedDB: opening a database, creating an object store, running add() and put() inside readwrite transactions, and handling success and errors safely.

What You’ll Learn

01

indexedDB.open

Open or upgrade DB.

02

Object store

Define schema.

03

add()

New records.

04

put()

Upsert rows.

05

Transactions

readwrite mode.

06

Errors

Callbacks & abort.

What Is IndexedDB?

IndexedDB is a transactional, key-value database built into modern browsers. Data is organized in object stores (similar to tables) inside a named database. Each record is a JavaScript object; you choose a keyPath (like id) or let the browser auto-generate keys with autoIncrement: true.

The API is asynchronous—operations use requests and event callbacks instead of blocking the main thread. That keeps your UI responsive while large datasets are read or written.

💡
Beginner Tip

Think of IndexedDB like a NoSQL database in the browser. You store whole JavaScript objects, not SQL rows. To insert, you open the database, start a readwrite transaction, and call objectStore.add(item).

Creating an IndexedDB Database

Before inserting data, open the database and define an object store where records will live. Schema changes happen only in onupgradeneeded when you bump the version number.

js
const request = indexedDB.open('myDatabase', 1);

request.onupgradeneeded = function (event) {
  const db = event.target.result;
  const store = db.createObjectStore('contacts', {
    keyPath: 'id',
    autoIncrement: true
  });
  store.createIndex('emailIndex', 'email', { unique: false });
};

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

request.onerror = function (event) {
  console.error('Open failed:', event.target.error.message);
};
  • indexedDB.open(name, version) — opens an existing database or creates a new one.
  • onupgradeneeded — runs when the version increases; create object stores and indexes here.
  • keyPath: 'id' — tells IndexedDB which property is the primary key.
  • autoIncrement: true — generates numeric ids when you omit id on insert.
  • createIndex — optional secondary lookup on email (used later for queries).

Opening a Database

After the first setup, you open the same database on every page load. If the version matches, onupgradeneeded does not run—you go straight to onsuccess.

js
const request = indexedDB.open('myDatabase', 1);

request.onsuccess = function (event) {
  const db = event.target.result;
  // db is ready — start transactions here
};

request.onerror = function (event) {
  console.error('Database error:', event.target.error.message);
};

Keep a reference to db in a variable or module. Close it with db.close() when your app unloads if you manage connections manually.

Inserting Data

To insert a record, start a readwrite transaction on the object store, then call add() with a plain JavaScript object:

js
const request = indexedDB.open('myDatabase', 1);

request.onsuccess = function (event) {
  const db = event.target.result;
  const tx = db.transaction(['contacts'], 'readwrite');
  const store = tx.objectStore('contacts');

  const item = { name: 'John Doe', age: 30 };
  const addRequest = store.add(item);

  addRequest.onsuccess = function () {
    console.log('Added with id:', addRequest.result);
  };

  addRequest.onerror = function (event) {
    console.error('Add failed:', event.target.error.message);
  };
};

With autoIncrement: true, you can omit id—IndexedDB assigns one and returns it in addRequest.result. If you provide an id that already exists, add() fails with a ConstraintError.

add() vs put()

  • store.add(item) — insert only; fails if the key already exists.
  • store.put(item) — insert or replace; updates an existing record with the same key.

Handling Transactions

Transactions group one or more operations. For inserts, always use 'readwrite' mode and listen for completion and errors:

js
const tx = db.transaction(['contacts'], 'readwrite');

tx.oncomplete = function () {
  console.log('Transaction committed successfully');
};

tx.onerror = function (event) {
  console.error('Transaction error:', event.target.error.message);
};

const store = tx.objectStore('contacts');
store.add({ name: 'Jane Doe', age: 25 });

If any request in the transaction fails and the error is not handled, the transaction aborts and none of its writes are saved. Chain multiple add() calls in one transaction for atomic batch inserts.

Error Handling

IndexedDB errors can occur when opening the database, exceeding storage quotas, using a closed connection, or violating key constraints. Handle errors at both the request and transaction levels:

js
const openReq = indexedDB.open('myDatabase', 1);

openReq.onerror = function (event) {
  console.error('Open error:', event.target.error.message);
};

openReq.onsuccess = function (event) {
  const db = event.target.result;
  const tx = db.transaction(['contacts'], 'readwrite');

  tx.onerror = function (event) {
    console.error('Transaction aborted:', event.target.error.message);
  };

  const addReq = tx.objectStore('contacts').add({ id: 1, name: 'Sam' });

  addReq.onerror = function (event) {
    console.error('Add error:', event.target.error.message);
    /* Duplicate key? Try put() instead of add(). */
  };
};

Note: use event.target.error.message, not the legacy errorCode property shown in older tutorials.

Common Pitfalls

  1. Schema in the wrong place — create object stores only in onupgradeneeded, not inside a normal insert transaction.
  2. Wrong transaction modeadd() and put() require 'readwrite'; 'readonly' will fail.
  3. Duplicate keys with add() — calling add() twice with the same id throws; use put() for upserts.
  4. Version bumps — to add a new object store later, increment the version in open() and migrate in onupgradeneeded.
  5. Storing non-cloneable values — objects must be structured-cloneable (no functions or DOM nodes in the record).

⚡ Quick Reference

OperationCode pattern
Open databaseindexedDB.open('name', version)
Create storedb.createObjectStore('store', { keyPath: 'id' })
Write transactiondb.transaction(['store'], 'readwrite')
Insert new recordstore.add({ name: '...' })
Upsert recordstore.put({ id: 1, name: '...' })
New auto idaddRequest.result after success
Open
indexedDB.open(...)

Connect

Transaction
'readwrite'

Required

Insert
store.add(item)

New key

Upsert
store.put(item)

Add/update

Examples Gallery

These examples follow the IndexedDB insert pattern. Use Try It Yourself to run live demos in the editor. IndexedDB works in all modern browsers.

📚 Getting Started

Open the database, create a store, and add your first record.

Example 1 — Add a Single Record

Minimal insert with autoIncrement so IndexedDB assigns the id.

js
const openReq = indexedDB.open('myDatabase', 1);

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

openReq.onsuccess = function (event) {
  const db = event.target.result;
  const tx = db.transaction(['contacts'], 'readwrite');
  const addReq = tx.objectStore('contacts').add({
    name: 'John Doe',
    email: 'john@example.com'
  });

  addReq.onsuccess = function () {
    console.log('New id:', addReq.result);
  };
};
Try It Yourself

How It Works

The object omits id; autoIncrement generates 1 for the first record. addRequest.result returns the assigned key.

Example 2 — Reusable addContact Function

Wrap inserts in a function you can call from buttons or form submit handlers.

js
function addContact(db, contact) {
  return new Promise(function (resolve, reject) {
    const tx = db.transaction(['contacts'], 'readwrite');
    const addReq = tx.objectStore('contacts').add(contact);

    addReq.onsuccess = function () {
      resolve(addReq.result);
    };
    addReq.onerror = function () {
      reject(addReq.error);
    };
  });
}

addContact(db, { name: 'Notebook', email: 'shop@example.com' })
  .then(function (id) { console.log('Saved id', id); })
  .catch(function (err) { console.error(err.message); });
Try It Yourself

How It Works

Wrapping callbacks in a Promise makes async insert code easier to read. Validate contact before calling add().

📈 Practical Patterns

Batch inserts, upserts, and a complete HTML demo.

Example 3 — Batch Insert in One Transaction

Add multiple records atomically—if one fails, none are committed.

js
const contacts = [
  { name: 'Apple', email: 'a@fruit.com' },
  { name: 'Banana', email: 'b@fruit.com' },
  { name: 'Cherry', email: 'c@fruit.com' }
];

const tx = db.transaction(['contacts'], 'readwrite');
const store = tx.objectStore('contacts');

contacts.forEach(function (c) {
  store.add(c);
});

tx.oncomplete = function () {
  console.log('All', contacts.length, 'contacts saved');
};
Try It Yourself

How It Works

All add() calls share one transaction. tx.oncomplete fires only when every request succeeds.

Example 4 — put() for Upsert

Save the same id twice—put() updates instead of failing.

js
function saveNote(db, note) {
  const tx = db.transaction(['notes'], 'readwrite');
  tx.objectStore('notes').put(note);
}

saveNote(db, { id: 1, title: 'Draft' });
saveNote(db, { id: 1, title: 'Final version' }); /* replaces first */
Try It Yourself

How It Works

Use add() when duplicates must be rejected; use put() for “save” buttons that create or update in one step.

Example 5 — Example Usage (Complete HTML Page)

A full page that creates the database and inserts data when the user clicks a button.

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>IndexedDB Insert Example</title>
</head>
<body>
  <h1>IndexedDB Insert Example</h1>
  <button type="button" id="addData">Add Contact</button>
  <p id="status"></p>

  <script>
    let db;

    const dbRequest = indexedDB.open('myDatabase', 1);

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

    dbRequest.onsuccess = function (event) {
      db = event.target.result;
      document.getElementById('status').textContent = 'Database ready.';
    };

    dbRequest.onerror = function (event) {
      document.getElementById('status').textContent =
        'Error: ' + event.target.error.message;
    };

    document.getElementById('addData').addEventListener('click', function () {
      const tx = db.transaction(['contacts'], 'readwrite');
      const addReq = tx.objectStore('contacts').add({
        name: 'Jane Doe',
        age: 25
      });

      addReq.onsuccess = function () {
        document.getElementById('status').textContent =
          'Added contact id ' + addReq.result;
      };

      addReq.onerror = function (event) {
        document.getElementById('status').textContent =
          'Add failed: ' + event.target.error.message;
      };
    });
  </script>
</body>
</html>
Try It Yourself

How It Works

The page opens the database once on load. Each button click starts a new readwrite transaction and shows the new id in the status paragraph.

🚀 Common Use Cases

  • Offline form drafts — save user input locally before syncing to a server.
  • Shopping cart cache — persist cart items between page reloads.
  • Activity logs — append timestamped events to a local store.
  • Progressive Web Apps — queue actions for background sync when offline.
  • Media caches — store blobs (images, PDFs) alongside metadata objects.

🧠 How IndexedDB Insert Works

1

Open database

indexedDB.open() connects; upgrade creates stores.

Connect
2

Start transaction

db.transaction(['store'], 'readwrite')

Transaction
3

add() or put()

Write a structured-cloneable JavaScript object.

Write
=

Record saved

onsuccess fires; data persists until site data is cleared.

📝 Notes

  • Create object stores only in onupgradeneeded, not during normal inserts.
  • Inserts require a readwrite transaction on the correct store name.
  • Validate data in JavaScript before writing (required fields, types, size limits).
  • add() rejects duplicate keys; put() overwrites them.
  • Storage is per-origin; other sites cannot read your IndexedDB data.
  • For reading inserted data, continue to the Retrieve tutorial.

Universal Browser Support

IndexedDB is supported in all modern browsers: Chrome, Firefox, Safari, Edge, and mobile WebViews. It is the W3C-recommended API for structured client-side storage, unlike the deprecated Web SQL API.

Baseline · Since HTML

IndexedDB API

IndexedDB is supported in all modern browsers: Chrome, Firefox, Safari, Edge, and mobile WebViews. It is the W3C-recommended API for structured client-side storage, unlike the deprecated Web SQL API.

95% Modern browser support
Google Chrome All versions · Desktop & Mobile
Full support
Mozilla Firefox All versions · Desktop & Mobile
Full support
Apple Safari All versions · macOS & iOS
Full support
Microsoft Edge All versions · Chromium & Legacy
Full support
Internet Explorer IE 6+ · Legacy environments
Full support
Opera All modern versions
Full support
IndexedDB API Excellent

Bottom line: Safe to use IndexedDB for new cross-browser web apps. Feature-detect with window.indexedDB if you need to support very old environments.

💡 Best Practices

✅ Do

  • Define schema in onupgradeneeded with a clear version strategy
  • Validate objects before add() or put()
  • Handle request and transaction error callbacks
  • Use put() for save/upsert and add() for strict inserts
  • Batch related writes in a single transaction
  • Wrap callback APIs in Promises for cleaner async code

❌ Don’t

  • Create object stores outside onupgradeneeded
  • Assume add() succeeds without an error handler
  • Store functions, DOM nodes, or non-cloneable values
  • Open a new database connection for every single insert
  • Forget to bump the version when adding new stores
  • Rely on localStorage for large structured datasets

Conclusion

Inserting data into IndexedDB means opening a database, ensuring your object store exists (in onupgradeneeded), and calling add() or put() inside a readwrite transaction with proper error handling.

IndexedDB is the right choice for modern offline-capable web apps. Once you can insert records confidently, learn how to read them in the Retrieve tutorial and how to change them in UPDATE.

Key Takeaways

Knowledge Unlocked

Five things to remember about IndexedDB insert

Use these points when adding records to a client-side database.

5
Core concepts
02

add()

New keys.

Insert
🔄 03

put()

Upsert.

Update
🔒 04

readwrite

Tx mode.

Required
⚠️ 05

onerror

Handle fail.

Reliability

❓ Frequently Asked Questions

Open the database with indexedDB.open(), create an object store in onupgradeneeded if needed, then start a readwrite transaction: db.transaction(['storeName'], 'readwrite'). Get the object store and call store.add(object) for new records or store.put(object) to insert or update.
add() fails with a ConstraintError if a record with the same key already exists. put() writes the record regardless—creating it if missing or replacing the existing one. Use add() for strict inserts and put() for upsert-style saves.
IndexedDB groups reads and writes in transactions for consistency. A readwrite transaction is required for add() and put(). If the transaction aborts, none of its writes are committed.
Create object stores only inside the onupgradeneeded event when you open the database with a higher version number. Schema changes cannot run during a normal readwrite transaction on an already-open database.
Listen to request.onerror on the add/put request and transaction.onerror on the transaction. Read event.target.error.message, show user feedback, and call event.preventDefault() on transaction errors if you want to suppress automatic abort logging in some cases.
Yes. IndexedDB is supported in all current major browsers including Chrome, Firefox, Safari, and Edge. It is the recommended API for structured client-side storage in modern web apps.
Did you know?

IndexedDB uses the structured clone algorithm to copy objects into storage. That is the same mechanism used by postMessage between workers—so plain objects, arrays, Dates, and Blobs work, but functions and DOM elements do not.

Practice IndexedDB insert

Open a database, create a contacts store, and add records in the Try It editor—see the auto-generated id on each click.

Open Try It editor →

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.

6 people found this page helpful