Creating and Querying Indexes
Why indexes?
Section titled “Why indexes?”Every IndexedDB object store is keyed by its primary key (the keyPath or an auto-incremented id). That is efficient for lookups by primary key, but if you want to query by a different field — for example, find all contacts by their name — you need an index.
An index is a secondary sorted structure that maps a chosen field’s value back to the record’s primary key. Once created, you can call get() or getAll() on the index exactly as you would on the store itself.
Creating an index with createIndex()
Section titled “Creating an index with createIndex()”Indexes must be created (or deleted) inside the onupgradeneeded callback. You cannot add an index to an existing store outside of an upgrade transaction.
const request = indexedDB.open('mydb', 1);
request.onupgradeneeded = (event) => { const db = event.target.result;
// Create the object store const store = db.createObjectStore('contacts', { keyPath: 'id' });
// Create an index on the 'name' field store.createIndex('by_name', 'name', { unique: false });};createIndex(name, keyPath, options) parameters
Section titled “createIndex(name, keyPath, options) parameters”| Parameter | Purpose |
|---|---|
name | The index name you will use to retrieve it later with store.index(name) |
keyPath | The property path on each record to index (e.g. 'name', 'address.city') |
unique | If true, the store rejects records whose indexed value duplicates an existing one |
multiEntry | If true and the indexed property is an array, each array element gets its own entry in the index |
Opening the index and querying
Section titled “Opening the index and querying”After the upgrade, open the index from a normal read/write transaction with store.index('by_name'), then call get(value) for a single record or getAll(value) for all matching records.
const tx = db.transaction('contacts', 'readonly');const store = tx.objectStore('contacts');const nameIndex = store.index('by_name');
// Single matchconst getReq = nameIndex.get('Alice');getReq.onsuccess = () => console.log(getReq.result); // { id: 1, name: 'Alice', ... }
// All records (no filter → returns every record sorted by index key)const getAllReq = nameIndex.getAll();getAllReq.onsuccess = () => console.log(getAllReq.result);Passing a value to getAll(value) filters to records whose indexed field equals that value. Passing no argument (or undefined) returns every record in the store, ordered by the index key.
The unique flag
Section titled “The unique flag”// Enforce unique emailsstore.createIndex('by_email', 'email', { unique: true });With unique: true, any add() or put() that would create a duplicate indexed value throws a ConstraintError. Use this for fields like email addresses or usernames where duplicates are not allowed.
The multiEntry flag
Section titled “The multiEntry flag”// Record: { id: 1, tags: ['js', 'idb', 'storage'] }store.createIndex('by_tag', 'tags', { multiEntry: true });With multiEntry: true, an array-valued field produces one index entry per element. You can then call index.getAll('idb') and the record above will be returned even though the value of tags is an array.
Runnable: create an index and query it
Section titled “Runnable: create an index and query it”Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Creating an index on a frequently-queried field | Much faster reads/searches on that field, no need to scan every record | Every write must also update the index, slowing writes down and using extra storage |
| No index at all (search via cursor + your own filter) | No write overhead, no extra storage used | Reads/searches must scan every record, getting slower as the store grows |
multiEntry: true on an array field | Efficiently searches each element inside an array | Adds one index entry per array element, so the index grows with the element count |
Common mistakes
Section titled “Common mistakes”- Trying to create a new index outside
onupgradeneeded— this throws immediately. Indexes can only be created or removed inside an upgrade transaction. - Setting
unique: trueon a field that already has duplicate values — writing a record with a duplicate indexed value throwsConstraintErrorimmediately. Make sure the field is genuinely unique before turning this on. - Forgetting that
getAll()on an index sorts by the index key, not the primary key — if you expect primary-key order but query through an index, the results are instead sorted by the indexed field.