LEARNING OBJECTIVES ⌵
- Understand the role of an
IDBObjectStoreas the primary storage collection in IndexedDB. - Differentiate between the four fundamental keying strategies in IndexedDB.
- Implement composite (multi-field) primary keys using array
keyPathdefinitions. - Identify valid vs invalid IndexedDB key data types according to the W3C specification.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine a modern warehouse fulfillment center.
An ObjectStore is like a specialized storage aisle in the warehouse (for example, the "Electronics Aisle" or the "Customer Accounts Aisle"). Every item placed on the shelves must have an inventory tracking identifier—a Primary Key—so workers can instantly pinpoint and retrieve it.
How do items get their inventory tracking numbers?
- The In-Line Barcode (
keyPath: "sku"): The manufacturer prints the barcode directly onto the product's packaging. When you receive the product, the warehouse system reads the existingskuproperty inside the object itself. - The In-Line Smart Stamper (
keyPath: "id", autoIncrement: true): The product arrives as a blank box without an ID. The receiving machine automatically laser-prints an incremental number (1, 2, 3...) right onto the product and records that number in the object'sidfield. - The Out-of-Line Shelf Tag (
autoIncrement: false, no keyPath): The items are plain unmarked goods. The warehouse manager manually writes an external tag on the shelf slot (e.g.,"slot-A9") during delivery (store.add(item, "slot-A9")). The item itself has no clue what slot it sits in. - The Out-of-Line Auto-Ticket Machine (
autoIncrement: true, no keyPath): Unmarked goods enter the conveyor belt, and a ticket dispenser hands the worker a sequential receipt number (1, 2, 3...) while placing the item on the shelf.
Technical Deep Dive & Specifications
The Four Primary Keying Strategies
When creating an ObjectStore using db.createObjectStore(storeName, options), you configure how keys are resolved for every record:
+----------------------------------------------------------------------------------------------------+
| INDEXEDDB KEYING STRATEGY MATRIX |
+-------------------+---------------------+-------------------------+--------------------------------+
| Key Strategy | createObjectStore | How Key is Determined | Example store.add() Call |
| | Options | | |
+-------------------+---------------------+-------------------------+--------------------------------+
| 1. In-Line Only | { keyPath: "id" } | Extracted directly from | `store.add({ id: "u_1", ...})` |
| | | the object property | (Fails if 'id' is missing) |
+-------------------+---------------------+-------------------------+--------------------------------+
| 2. In-Line + Auto | { keyPath: "id", | If object has 'id', it | `store.add({ name: "Dan" })` |
| Increment | autoIncrement: | is used; otherwise, | (Auto-assigns id: 1 to object) |
| | true } | generator creates one | |
+-------------------+---------------------+-------------------------+--------------------------------+
| 3. Out-of-Line | { } | Must pass key as second | `store.add({ name: "Eva" }, |
| Manual | (no options) | parameter in add()/put()| "user_eva_99")` |
+-------------------+---------------------+-------------------------+--------------------------------+
| 4. Out-of-Line | { autoIncrement: | Generator creates key; | `store.add({ name: "Frank" })` |
| Auto Increment | true } | returns key on success | (Returns key 1; object unmod.) |
+-------------------+---------------------+-------------------------+--------------------------------+
Valid vs Invalid Key Types
IndexedDB enforces strict rules regarding what JavaScript values qualify as valid keys:
+---------------------------------------------------------------------------------+
| VALID KEY TYPES |
+---------------------------------------------------------------------------------+
| ✅ String | Any valid UTF-16 string (e.g. "order_9812", "[email protected]") |
| ✅ Number | Any finite IEEE 754 number (e.g. 1, 42.5). NOT NaN or Infinity!|
| ✅ Date | Valid Date objects (e.g. new Date()). NOT Invalid Date! |
| ✅ ArrayBuffer | Binary buffer instances (or TypedArray views) |
| ✅ Array | Arrays where EVERY element is itself a valid key (Compound Key) |
+---------------------------------------------------------------------------------+
| INVALID KEY TYPES |
+---------------------------------------------------------------------------------+
| ❌ Boolean | true, false (Throws DataError) |
| ❌ Null / Undef | null, undefined (Throws DataError) |
| ❌ Plain Object | { foo: "bar" } cannot be a key (Throws DataError) |
| ❌ RegExp / Map | Non-primitive collections and regular expressions |
+---------------------------------------------------------------------------------+
Compound (Composite) Primary Keys
You can define a composite primary key by passing an array of property paths to keyPath:
// Compound key: Unique combination of tenantId and invoiceNumber
const store = db.createObjectStore('invoices', {
keyPath: ['tenantId', 'invoiceNumber']
});
// Storing a record:
store.add({
tenantId: 'acme_corp',
invoiceNumber: 10452,
amount: 4500.00
});
// Key in store is evaluated as: ['acme_corp', 10452]
💻 Interactive Code Playground
Starter Code
Save this file as object-stores.html and open it in your browser.
Line-by-Line Code Breakdown
- Line 55 (
d.createObjectStore('articles', { keyPath: 'id', autoIncrement: true })): Generates an auto-incrementing integer key and writes it directly into the object'sidproperty. - Line 57 (
d.createObjectStore('blobs')): Creates an out-of-line store. Every insertion requires passing the key as the second parameter:store.add(data, key). - Line 59 (
d.createObjectStore('metrics', { keyPath: ['server', 'timestamp'] })): Configures a compound key. An entry is valid only if bothserverandtimestampcontain valid key data types. - Lines 70–75 (
store.add(article)): Notice that whenautoIncrementis combined withkeyPath: 'id', IndexedDB mutates the in-memory object by attaching the generatedid. - Lines 104–109 (
store.add({ data: 1 }, true)): Demonstrates that passing aBoolean(true) as a primary key violates the IndexedDB specification and immediately throws aDataError.
Expected Browser Render Output
[02:40:01] 🛠️ Creating 3 distinct ObjectStores...
[02:40:01] ✅ KeySchemesDB ready for testing.
[02:40:05] 📰 Article inserted! Generated key: 1, Object id: 1
[02:40:08] 📦 Blob stored with out-of-line key: "asset_logo_1718000100"
[02:40:11] 📈 Compound Metric stored! Key: ["us-east-1", 1718000103]
[02:40:14] ⚠️ Attempting store.add({ data: 1 }, true) [Boolean Key]...
[02:40:14] ❌ Caught Expected Error: DataError - The data provided to an operation does not meet requirements.🏋️ Hands-On Exercise
🎯 The Challenge: Multi-Tenant Schema Architect
Instructions:
- Create an IndexedDB database named
SaaSPlatformat Version 1. - In
onupgradeneeded, create an ObjectStore namedtenant_filesconfigured with:- A compound primary key consisting of
tenantIdandfilePath.
- A compound primary key consisting of
- Insert two records for
tenantId: "tenant_alpha":{ tenantId: "tenant_alpha", filePath: "/docs/readme.txt", size: 1024 }{ tenantId: "tenant_alpha", filePath: "/images/hero.png", size: 204800 }
- Attempt to insert a duplicate record with the exact same
tenantIdandfilePathand verify that IndexedDB triggers aConstraintError.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Supplying an Out-of-Line Key to an In-Line Store: If a store was defined with
keyPath: "id", executingstore.add({ id: 1, name: "A" }, 1)throws anInvalidAccessError: Failed to execute 'add' on 'IDBObjectStore': The object store uses in-line keys and the key parameter was provided. - Using Invalid Key Types in
keyPath: Attempting to usetrue,false,null, or an empty object as a primary key throws aDataError. - Modifying In-Line Key Properties Post-Creation: If you update an object and change its
keyPathvalue, callingstore.put(updatedObj)will insert a brand new record instead of updating the existing one.
💡 Pro Tips
- Leverage Composite Keys for Hierarchical Isolation: In multi-tenant, workspace-based, or folder-based web apps, using compound keys like
['workspaceId', 'documentId']provides instant tenant isolation without requiring separate ObjectStores. - Use UUIDs / CUIDs for Distributed Offline Systems: When building offline-first apps that sync with a server, prefer client-generated UUIDv4 or ULID strings over
autoIncrement: trueto prevent ID collisions during multi-device synchronization.
📌 Key Takeaways
- An
IDBObjectStoreis the core container in IndexedDB, holding records organized by primary keys. - In-line keys extract the primary key from an internal property (
keyPath), while out-of-line keys are passed as explicit arguments. autoIncrement: trueinstructs the browser engine to generate sequential numeric keys automatically.- Compound keys (
keyPath: ['fieldA', 'fieldB']) enforce uniqueness across multiple properties. - Numbers, strings, Dates, ArrayBuffers, and Arrays of valid keys are legal key types; Booleans, objects, and null values are illegal.
- --