Skip to content

CRUD Operations

Every data operation in IndexedDB goes through an IDBObjectStore. You obtain a store from a transaction, then call one of these six methods:

MethodMode neededDescription
add(value)readwriteAdds a new record; throws ConstraintError if the key already exists
put(value)readwriteAdds or overwrites (upsert); never throws on duplicate key
get(key)readonlyReturns one record, or undefined if the key is absent
getAll()readonlyReturns an array of all records in the store
delete(key)readwriteRemoves the record with the given key; no-op if absent
count()readonlyReturns the total number of records

Every method returns an IDBRequest. The result is available inside the request’s onsuccess callback as request.result.

add(value) is strictly for new records. If a record with the same key path value already exists, the request fires onerror with a ConstraintError — the record is not written.

put(value) is an upsert: it writes the record whether or not the key already exists. Use put when you want “create or update” semantics without checking first.

const tx = db.transaction(['products'], 'readwrite');
const store = tx.objectStore('products');
// add — safe only when id 1 does not exist yet
store.add({ id: 1, name: 'Apple', price: 1.5 });
// put — always safe; overwrites if id 1 is already there
store.put({ id: 1, name: 'Apple', price: 1.99 });

This is different from Web Storage, where a missing key returns null. With IndexedDB, get(key) resolves to undefined when no record matches. Always check === undefined, not !result, to distinguish a missing record from a record that stores a falsy value.

const tx = db.transaction(['products'], 'readonly');
const store = tx.objectStore('products');
const req = store.get(999);
req.onsuccess = () => {
console.log(req.result); // undefined — key 999 does not exist
};

Raw IDBRequest callbacks are verbose. A tiny helper makes every store method await-able:

function promisifyReq(r) {
return new Promise((res, rej) => {
r.onsuccess = () => res(r.result);
r.onerror = () => rej(r.error);
});
}
// Now you can await any store method
const apple = await promisifyReq(store.get(1));
Browser Storage
You call store.add({ id: 1, name: "Apple" }) when a record with id 1 already exists. What happens?
What does store.get(999) resolve to when no record has key 999?
Which transaction mode is required to call store.delete(key)?
You want to update a record if it exists, or insert it if it does not — in one call. Which method do you use?