Object Stores and Keys
What Is an Object Store?
Section titled “What Is an Object Store?”An object store is IndexedDB’s equivalent of a database table. Each record in an object store is a JavaScript object — but unlike a SQL table, there is no schema enforcement. You can store objects with different shapes in the same store. The only structural rule is how the key is determined.
Think of it this way:
- A SQL table row has a fixed set of columns.
- An object store record is any JS object you hand it, plus a key that uniquely identifies it.
Creating an Object Store
Section titled “Creating an Object Store”You create object stores by calling db.createObjectStore(name, options) on the database instance. This method must be called inside the onupgradeneeded event handler — it is only valid during a version upgrade.
const request = indexedDB.open('my-db', 1);
request.onupgradeneeded = (event) => { const db = event.target.result; db.createObjectStore('contacts', { keyPath: 'id' });};The first argument is the store name. The second argument is an options object controlling how keys work.
keyPath: In-Line Keys
Section titled “keyPath: In-Line Keys”When you set keyPath, the store uses a property on the stored object as its key. This is called an in-line key because the key lives inside the object itself.
// Every object you store MUST have an `id` propertydb.createObjectStore('contacts', { keyPath: 'id' });
// Valid record:
// Invalid — no `id` field, will throw:store.add({ name: 'Bob' });If you set autoIncrement: true alongside keyPath, the browser generates the key automatically and writes it into the object at the specified path:
db.createObjectStore('contacts', { keyPath: 'id', autoIncrement: true });
// `id` will be filled in automatically:store.add({ name: 'Alice' }); // stored as { id: 1, name: 'Alice' }Out-of-Line Keys
Section titled “Out-of-Line Keys”When you omit keyPath (or set it to null), the key is separate from the object. This is called an out-of-line key. You supply it manually as the second argument to add() or put().
db.createObjectStore('raw-blobs', { keyPath: null });
// You provide the key explicitly:store.add({ data: '...' }, 'blob-001');Combine with autoIncrement: true to let the browser generate integer keys without touching the stored object:
db.createObjectStore('logs', { autoIncrement: true });
// No key needed — browser assigns 1, 2, 3, ...store.add({ level: 'info', message: 'App started' });Key Mode Comparison
Section titled “Key Mode Comparison”| Mode | keyPath | autoIncrement | Key source |
|---|---|---|---|
| In-line (explicit) | 'id' | false | Object’s id field |
| In-line (auto) | 'id' | true | Auto-generated, stored in id |
| Out-of-line (manual) | (none) | false | Caller provides key |
| Out-of-line (auto) | (none) | true | Auto-generated, not in object |
Try It: Creating Two Object Stores
Section titled “Try It: Creating Two Object Stores”The example below opens a database at version 1 and creates two stores:
contacts— in-line key using theidpropertylogs— out-of-line auto-incrementing key
Run it to see both store names printed to the console.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Schemaless object store | Store differently-shaped objects in the same store, and restructure freely without a schema migration | No enforced validation — a wrong field or type shows up at runtime instead |
In-line key (keyPath) | The key lives inside the object itself — easy to read and debug, no separate key to pass | Every object must always have the keyPath field, or add/put throws |
| Out-of-line key | Keeps the key separate from the object, so you can store objects with no identity field (e.g. a blob) | You must supply the key yourself on every add/put call, adding boilerplate |
Common mistakes
Section titled “Common mistakes”- Calling
createObjectStore()outside ofonupgradeneeded— this method throws aDOMExceptionimmediately when called outside the upgrade transaction, since schema changes are only allowed during a version upgrade. - Not checking
objectStoreNames.contains()before creating a store again — once you bump the version,onupgradeneededre-runs your old migration code too, and callingcreateObjectStorewith a name that already exists throws. - Assuming an out-of-line key is stored inside the object — when
keyPathisn’t set, the key never appears in the object returned byget()/getAll(). You must read it fromIDBCursor.keyor the argument you passed toadd()instead.