What is objectStoreNames?
objectStoreNames is a read-only property of the IDBDatabase interface that returns a DOMStringList containing the names of all object stores currently in the connected database. This property is fundamental for understanding and managing the schema of your IndexedDB databases.
When you open an IndexedDB connection, the objectStoreNames property gives you immediate visibility into what data containers exist within that database. Whether you're building a new application or maintaining an existing one, this property serves as your primary tool for inspecting database structure at runtime.
The property is available in both the main thread and Web Workers, making it essential for any IndexedDB-powered application. Understanding how to use it effectively is a core skill for developers working with client-side data persistence.
The Role of Object Stores in IndexedDB
Object stores are the fundamental data containers in IndexedDB, functioning similarly to tables in relational databases but with key architectural differences. Each object store:
- Holds records indexed by a unique key path
- Can store complex JavaScript objects directly without serialization
- Supports multiple indexes for efficient querying and retrieval
- Functions as an independent data bucket within your database schema
The objectStoreNames property provides direct insight into which stores exist in your database at any given moment. This becomes particularly valuable during database version upgrades when you need to determine what already exists before creating new structures.
Understanding this relationship between objectStoreNames and the actual object stores helps you build more robust database initialization logic. Instead of assuming a fixed schema, you can query the current state and make informed decisions about what needs to be created or modified.
Fundamentals of objectStoreNames
The DOMStringList Return Type
The DOMStringList returned by objectStoreNames is an array-like object with specific characteristics that distinguish it from standard JavaScript arrays. Understanding these differences is crucial for writing effective IndexedDB code.
The DOMStringList interface provides several key capabilities:
- length property: Returns the number of store names currently in the database
- Indexed access: Access individual names via bracket notation like
db.objectStoreNames[0] - contains() method: Perform O(1) existence checks for specific store names
- Array-like iteration: Works with for...of loops and spread operators in modern browsers
However, DOMStringList is not a standard JavaScript Array, which means it doesn't include familiar Array methods like map(), filter(), or forEach(). For operations requiring these methods, you can convert the list to an array using Array.from(db.objectStoreNames).
This conversion pattern is particularly useful when you need to perform complex operations on store names or pass them to functions expecting arrays. As noted in the MDN Web Docs, understanding this distinction helps avoid common pitfalls when working with the IndexedDB API.
Understanding Database Schema Visibility
The objectStoreNames property reflects the current state of your database schema. During a database upgrade (the onupgradeneeded event), this property shows the stores that exist from the previous version, not the stores that will exist after your upgrade completes. This distinction is critical for writing correct version migration logic.
By leveraging objectStoreNames, you can dynamically inspect your database's structure and make informed decisions about schema modifications. This approach enables flexible database initialization patterns that work across different user states, from fresh installations to long-running applications with complex version histories.
1// Access objectStoreNames2const request = indexedDB.open('myDatabase');3 4request.onsuccess = function(event) {5 const db = event.target.result;6 7 // Check number of stores8 console.log('Number of stores:', db.objectStoreNames.length);9 10 // Access store by index11 console.log('First store:', db.objectStoreNames[0]);12 13 // Check if a store exists14 console.log('Has users store:', db.objectStoreNames.contains('users'));15 16 // Iterate over all stores17 for (let i = 0; i < db.objectStoreNames.length; i++) {18 console.log('Store:', db.objectStoreNames[i]);19 }20 21 // Modern iteration with for...of22 for (const storeName of db.objectStoreNames) {23 console.log('Store name:', storeName);24 }25 26 // Convert to Array for flexible operations27 const allStores = Array.from(db.objectStoreNames);28 console.log('All stores as array:', allStores);29};Practical Usage Patterns
Checking Store Existence Before Creation
The most important pattern for objectStoreNames is checking whether a store exists before attempting to create it. This prevents errors when initializing databases, running version upgrades, or refactoring database initialization code.
As documented in web.dev's IndexedDB guide, attempting to create an object store that already exists throws a ConstraintError. The objectStoreNames.contains() method provides a safe way to conditionally create stores without risking these errors.
The pattern is straightforward: always wrap createObjectStore() calls within an existence check. This approach ensures your initialization code works correctly whether it's running on a brand new database or upgrading an existing one with stores from previous versions. For applications that require robust data handling, consider partnering with experts in backend development services to ensure your database architecture follows industry best practices.
Version-Based Store Creation
For applications that evolve over time, you need to create stores conditionally based on the database version. The switch-case pattern enables handling migrations from different versions while maintaining backward compatibility with existing user databases.
The intentional fall-through behavior in this pattern means each case handles migration from its version to the next. A new database starts at version 0 and falls through all cases, creating every necessary store. An existing database at version 1 only executes cases 1 and 2, adding new stores while preserving existing ones.
This approach ensures smooth upgrades for all users regardless of their current database version, whether they're on your first release or have been using the application for years. Implementing proper version migration patterns is essential for maintaining data integrity across application updates.
1// Best practice pattern for safe store creation2const dbName = 'myApplicationDB';3const dbVersion = 1;4 5const request = indexedDB.open(dbName, dbVersion);6 7request.onupgradeneeded = function(event) {8 const db = event.target.result;9 10 // Safe pattern: check before creating each store11 if (!db.objectStoreNames.contains('users')) {12 db.createObjectStore('users', { keyPath: 'id' });13 }14 15 if (!db.objectStoreNames.contains('products')) {16 db.createObjectStore('products', { keyPath: 'sku' });17 }18 19 if (!db.objectStoreNames.contains('orders')) {20 db.createObjectStore('orders', { keyPath: 'orderId' });21 }22 23 // You can also create indexes on newly created stores24 if (!db.objectStoreNames.contains('logs')) {25 const logsStore = db.createObjectStore('logs', { autoIncrement: true });26 logsStore.createIndex('timestamp', 'timestamp', { unique: false });27 }28};Version Migration Pattern
For applications that evolve, use a switch-case pattern to handle migrations from different database versions. This approach ensures each version transition is handled correctly.
The intentional fall-through behavior means each case handles migration from that specific version to the next. When a new database starts at version 0, it falls through all cases, creating every store needed. When an existing database is at version 1, it only executes cases 1 and 2, adding new stores while preserving existing data.
This pattern provides a systematic way to grow your database schema over time without breaking existing user installations. Whether a user just installed your application or has been using it for years, the migration logic adds the right stores for their version.
As covered in the JavaScript.info IndexedDB tutorial, this pattern is essential for production applications that need to handle schema evolution gracefully. Proper database schema management becomes increasingly important as applications scale and require more sophisticated data handling capabilities.
1const request = indexedDB.open('myAppDB', 3);2 3request.onupgradeneeded = function(event) {4 const db = event.target.result;5 const oldVersion = event.oldVersion || 0;6 7 switch (oldVersion) {8 case 0:9 // New database - create all stores10 if (!db.objectStoreNames.contains('users')) {11 db.createObjectStore('users', { keyPath: 'id' });12 }13 if (!db.objectStoreNames.contains('settings')) {14 db.createObjectStore('settings', { keyPath: 'key' });15 }16 // Fall through to next case for version 1 migration17 case 1:18 // Version 1 -> Version 2 migration19 if (!db.objectStoreNames.contains('logs')) {20 db.createObjectStore('logs', { autoIncrement: true });21 }22 case 2:23 // Version 2 -> Version 3 migration24 if (!db.objectStoreNames.contains('cache')) {25 const cacheStore = db.createObjectStore('cache', { keyPath: 'url' });26 cacheStore.createIndex('timestamp', 'timestamp');27 }28 }29};objectStoreNames in Transaction Context
IDBDatabase vs IDBTransaction
There are actually two places where objectStoreNames appears in the IndexedDB API--on the database object and on transaction objects. They serve different but complementary purposes in your application.
| Context | Purpose | Availability |
|---|---|---|
| IDBDatabase.objectStoreNames | Lists all stores in the database | Always available on open database |
| IDBTransaction.objectStoreNames | Lists stores accessible in this transaction | Only within transaction callbacks |
The database-level objectStoreNames is your primary tool for schema management. Use it within onupgradeneeded handlers to inspect what stores exist before creating new ones.
The transaction-level objectStoreNames reflects which stores are accessible within that specific transaction. When you create a transaction with db.transaction(['users'], 'readwrite'), the transaction's objectStoreNames only includes the 'users' store--not all stores in the database.
Understanding this distinction prevents incorrect assumptions about store availability. For schema operations like creating stores or indexes, always use the database-level property in your onupgradeneeded callback. For dynamic data access within operations, the transaction-level property can help you verify which stores are accessible.
This dual-context design follows the W3C Indexed Database specification and enables both robust schema management and flexible runtime operations. Implementing these patterns correctly is part of building reliable web applications that handle data effectively.
Best Practices
Pattern: Database Initialization Helper
Creating a reusable database manager class helps maintain consistent initialization patterns across your application. This approach centralizes store definitions and provides helper methods for common operations.
The DatabaseManager pattern encapsulates your schema logic in one place. Store definitions become configuration rather than scattered code, making it easier to add new stores or modify existing ones. The helper methods like getStoreNames() and hasStore() provide convenient abstractions over the raw IndexedDB API.
By passing store definitions as a configuration array, you decouple schema knowledge from initialization logic. This makes your code more maintainable and easier to test. The pattern also ensures consistent use of objectStoreNames.contains() checks throughout your application.
Pattern: Dynamic Store Access
For debugging, data export, or synchronization scenarios, you may need to dynamically access all stores. The objectStoreNames property enables this flexible approach.
The dynamic access pattern iterates over all stores and retrieves data from each one programmatically. This is invaluable for features like bulk exports, cache clearing, or synchronization with remote servers. Instead of hardcoding store names, your code adapts to whatever stores exist in the database.
As your application evolves and adds new stores, dynamic access patterns automatically include them without code changes. This future-proofs features that need to work with the entire database schema. For organizations looking to implement sophisticated data synchronization strategies, consider how AI automation services can enhance your data workflows and reduce manual overhead.
1class DatabaseManager {2 constructor(dbName, version, storeDefinitions) {3 this.dbName = dbName;4 this.version = version;5 this.storeDefinitions = storeDefinitions;6 this.db = null;7 }8 9 async open() {10 return new Promise((resolve, reject) => {11 const request = indexedDB.open(this.dbName, this.version);12 13 request.onerror = () => reject(request.error);14 request.onsuccess = () => {15 this.db = request.result;16 resolve(this);17 };18 19 request.onupgradeneeded = (event) => {20 const db = event.target.result;21 22 this.storeDefinitions.forEach(def => {23 // Use objectStoreNames to check before creating24 if (!db.objectStoreNames.contains(def.name)) {25 const store = db.createObjectStore(def.name, def.options);26 27 // Create indexes if defined28 if (def.indexes) {29 def.indexes.forEach(index => {30 store.createIndex(index.name, index.keyPath, index.options);31 });32 }33 }34 });35 };36 });37 }38 39 async getStoreNames() {40 return Array.from(this.db.objectStoreNames);41 }42 43 hasStore(storeName) {44 return this.db.objectStoreNames.contains(storeName);45 }46 47 async getAllDataFromStores() {48 const results = {};49 for (const storeName of this.db.objectStoreNames) {50 const transaction = this.db.transaction(storeName, 'readonly');51 const store = transaction.objectStore(storeName);52 53 const data = await new Promise((resolve, reject) => {54 const request = store.getAll();55 request.onsuccess = () => resolve(request.result);56 request.onerror = () => reject(request.error);57 });58 59 results[storeName] = data;60 }61 return results;62 }63}Browser Support
The objectStoreNames property has excellent browser support across all modern browsers:
| Browser | Support Since |
|---|---|
| Chrome/Edge | Version 23+ (2012) |
| Firefox | Version 15+ (2012) |
| Safari | Version 10+ (2016) |
| iOS Safari | Version 10+ (2016) |
| Chrome for Android | Supported |
This property is part of the Baseline Widely Available specification, making it safe to use in production applications without polyfills. According to Can I Use data, IndexedDB (and therefore objectStoreNames) is supported by approximately 97% of users worldwide.
The wide browser support means you can confidently use objectStoreNames in production applications targeting general web audiences. No feature detection or fallback code is necessary for this core IndexedDB functionality.
Summary
The objectStoreNames property is essential for working effectively with IndexedDB databases. Key takeaways:
-
Always check before creating: Use
objectStoreNames.contains()to prevent duplicate store errors and ConstraintError exceptions -
Understand the contexts: Database-level
objectStoreNameslists all stores for schema management; transaction-level lists accessible stores for data operations -
Enable dynamic operations: Iterate over stores with for loops or convert to arrays for flexible data access patterns
-
Support version migrations: Use switch-case patterns with intentional fall-through to handle schema changes across different application versions
-
Centralize initialization: Use helper classes like DatabaseManager to maintain consistent patterns and reduce errors
By mastering objectStoreNames, you build a foundation for robust IndexedDB-powered applications that handle schema changes gracefully. Whether you're building a new application or maintaining an existing one, these patterns ensure your client-side database operations remain reliable and maintainable. For teams looking to implement enterprise-grade web applications with sophisticated data management, our web development team can help architect scalable solutions that leverage these IndexedDB patterns effectively.
Sources
-
MDN Web Docs: IDBDatabase objectStoreNames - Core API documentation for the property, value type (DOMStringList), and basic examples
-
JavaScript.info: IndexedDB - Comprehensive tutorial covering IndexedDB fundamentals, version upgrades, and using objectStoreNames for schema management
-
web.dev: Work with IndexedDB - Google's developer guide with best practices for IndexedDB development and safe store creation patterns
-
W3C Indexed Database API 3.0 Specification - Official specification defining the objectStoreNames property behavior and IndexedDB architecture
Schema Inspection
Query the current database structure at runtime using the DOMStringList interface to understand what stores exist
Safe Store Creation
Prevent duplicate store errors by checking existence with contains() before calling createObjectStore()
Version Migration
Handle database schema changes across different application versions using conditional creation patterns
Dynamic Access
Iterate over stores programmatically for flexible data operations like exports and synchronization