Firestore Data Modeling: Subcollections vs. Top-Level Collections
Decide between Firestore subcollections and top-level collections based on ownership and query patterns. Learn how to balance security rules with global discoverability.
30 Jul 2026, 05:11 UTC

The Structural Dilemma: Hierarchy vs. Flatness
When designing a Cloud Firestore database, the primary architectural decision is how to relate entities. You must choose between subcollections (nesting data under a parent document) and top-level collections (using foreign keys to link documents). Choosing the wrong pattern often leads to expensive query refactors or restrictive security rules as the application scales.
The core trade-off is between ownership and discoverability. Subcollections imply a strict "belongs-to" relationship, while top-level collections treat entities as independent objects that happen to be related.
Comparison of Modeling Patterns
| Feature | Subcollections | Top-Level + Foreign Keys |
|---|---|---|
| Relationship | Hierarchical (Parent $\rightarrow$ Child) | Relational (Flat) |
| Query Scope | Scoped to parent by default | Global by default |
| Security Rules | Simplified via path inheritance | Requires field-level validation |
| Cross-Parent Query | Requires Collection Group Index | Standard query on foreign key |
| Data Portability | Difficult to move parents | Easy (update one field) |
When to Use Subcollections
Use subcollections for data that is logically owned by a parent and rarely accessed independently. A common example is users/{userId}/private_settings/{settingId}. The settings have no meaning without the user, and you will almost never need to query settings across all users simultaneously.
Advantages:
- Path-based Security: You can write a rule that grants access to any document under a specific user's path without checking a field inside the document.
- Logical Organization: The Firebase Console reflects the hierarchy, making manual debugging intuitive.
When to Use Top-Level Collections
Use top-level collections for entities that exist independently or need to be queried across the entire dataset. For example, in a social app, posts should be a top-level collection. While a post is created by a user, you frequently need to query "the 20 most recent posts from all users" or "posts tagged with #firebase."
Advantages:
- Query Flexibility: You can filter by any field (e.g.,
where('authorId', '==', '123')) without needing specialized indexes for every possible relationship. - Easier Migrations: If a post moves from one category to another, you update a single string field rather than deleting and recreating the document in a new path.
Implementation Example: The "Post" Entity
Consider a scenario where users create posts. We will implement this as a top-level collection to ensure global discoverability.
1. Data Structure
Create a collection named posts. Each document contains a reference to the user:
// Document path: /posts/{postId}
{
"title": "Firestore Modeling Guide",
"content": "...",
"authorId": "user_abc_123", // Foreign Key
"createdAt": Timestamp
}
2. Security Rules
Because the data is flat, the security rule must explicitly check the authorId field to prevent unauthorized edits. Run these in the Firebase Console Security Rules tab:
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
// Anyone can read posts
allow read: if true;
// Only the author can update or delete
allow write: if request.auth != null && request.auth.uid == request.resource.data.authorId;
}
}
}
3. Querying the Data
To fetch all posts for a specific user using the JavaScript SDK:
import { collection, query, where, getDocs } from "firebase/firestore";
const postsRef = collection(db, "posts");
const q = query(postsRef, where("authorId", "==", userId));
const querySnapshot = await getDocs(q);
Limitations and Validation
Index Requirements: Top-level collections require composite indexes if you filter by one field and order by another (e.g., filtering by authorId and ordering by createdAt). Firestore will provide a direct link to create this index in the error log if it is missing.
Referential Integrity: Firestore does not support cascading deletes. If you delete a user document in a top-level model, the associated posts remain as "orphaned" data. You must handle this via a Cloud Function that triggers on user deletion to clean up the posts collection.
Practical Verification
To verify your choice, perform the following checks in the Firebase Local Emulator Suite:
- Query Test: Attempt to fetch a list of items across multiple parents. If you used subcollections and find yourself needing a "Collection Group Query" for 80% of your views, migrate to a top-level collection.
- Rule Test: Verify that a user cannot edit a document by manually changing the ID in the request. If your security rules are becoming too complex to manage due to deep nesting, flatten the structure.
- Write Test: Measure the time to move an item from one parent to another. If it requires a
delete()followed by aset(), consider if a foreign key update would be more efficient.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.