firestore-data-modeling-patterns
Firestore data modeling best practices including subcollections, document structure, and atomic operations. PROACTIVELY activate for: (1) designing Firestore collection structures, (2) choosing between subcollections vs root collections, (3) implementing transactions vs batched writes. Triggers: "subcollection", "data model", "firestore"
What this skill does
# Firestore Data Modeling Patterns
## Overview
Firestore is a NoSQL document database that requires careful modeling to optimize for query patterns, scalability, and cost. This skill provides patterns for structuring data effectively.
## Subcollections vs. Root Collections
### When to Use Subcollections
**Pattern**: `users/{userId}/orders/{orderId}`
**Use When**:
- Data is tightly coupled to a parent (orders belong to a specific user)
- Child data is always accessed via parent
- You don't need to query children globally across all parents
- Document hierarchy makes semantic sense
**Example**:
```typescript
// User's private sessions (accessed only via user)
users/{userId}/sessions/{sessionId}
// User's notification preferences
users/{userId}/settings/notifications
```
**Critical Limitation**: Deleting a parent document **does NOT** delete subcollections. You must implement cleanup logic (e.g., Cloud Function).
### When to Use Root Collections
**Pattern**: Separate `users` and `posts` collections with reference fields
**Use When**:
- Need to query data globally (e.g., "all posts across all users")
- Data has many-to-many relationships
- Want simpler deletion semantics (no orphaned data risk)
- Need flexibility for future access patterns
**Example**:
```typescript
// posts collection
{
id: "post1",
authorId: "user123", // Reference to users collection
title: "...",
createdAt: Timestamp
}
// Query all posts by a user
postsRef.where('authorId', '==', 'user123').get()
// Query all posts globally
postsRef.orderBy('createdAt', 'desc').limit(10).get()
```
**Decision Matrix**:
| Criterion | Subcollection | Root Collection |
|-----------|--------------|-----------------|
| Query across parents | Requires Collection Group Query | Simple query |
| Deletion cascade | Manual cleanup needed | Independent lifecycle |
| Document limit (1MB) | Spreads data | Risk if embedding arrays |
| Semantic hierarchy | Clear parent-child | Relies on references |
## Document Structure Best Practices
### Embedding vs. Referencing
**Embed When**:
- Data is small and frequently accessed together
- 1-to-1 or 1-to-few relationships
- Data doesn't change frequently
```typescript
// User profile with embedded address
{
id: "user1",
name: "Alice",
address: {
street: "123 Main St",
city: "NYC",
zip: "10001"
}
}
```
**Reference When**:
- Data is large or changes frequently
- 1-to-many or many-to-many relationships
- Need to query the related data independently
```typescript
// Post references author
{
id: "post1",
title: "My Post",
authorId: "user1", // Reference
categoryIds: ["cat1", "cat2"] // Many-to-many
}
```
### Document Size Limits
- **Max Document Size**: 1MB
- **Max Array Size**: 20,000 elements (but practical limit is much lower for performance)
- **Max Nesting Depth**: 20 levels
## Atomic Operations
### Transactions (Read-Modify-Write)
**Use When**: Write depends on current document state
**Example**: Increment a counter
```typescript
import { runTransaction } from 'firebase/firestore';
await runTransaction(db, async (transaction) => {
const postRef = doc(db, 'posts', 'post1');
const postDoc = await transaction.get(postRef);
if (!postDoc.exists()) {
throw new Error('Post does not exist');
}
const newViewCount = postDoc.data().viewCount + 1;
transaction.update(postRef, { viewCount: newViewCount });
});
```
**Characteristics**:
- Reads must precede writes
- Automatic retries on conflicts
- Fails if offline
- Limited to 500 documents
### Batched Writes (Write-Only)
**Use When**: Multiple independent write operations need atomicity
**Example**: Create user + settings document
```typescript
import { writeBatch } from 'firebase/firestore';
const batch = writeBatch(db);
const userRef = doc(db, 'users', 'user1');
batch.set(userRef, {
name: 'Alice',
email: '[email protected]',
createdAt: serverTimestamp(),
});
const settingsRef = doc(db, 'users', 'user1', 'settings', 'notifications');
batch.set(settingsRef, {
emailNotifications: true,
pushNotifications: false,
});
await batch.commit(); // All succeed or all fail
```
**Characteristics**:
- Faster than transactions
- Works offline (queued)
- Up to 500 operations
- No reads allowed
**Decision Rule**: Default to batched writes (simpler, faster). Use transactions only when reads are required.
## Best Practices Summary
**Do**:
- Denormalize data for read-heavy applications
- Use root collections for flexibility
- Create composite indexes for complex queries
- Use batched writes for atomic multi-document updates
- Keep documents under 1MB
**Don't**:
- Embed large arrays or frequently-changing data
- Use subcollections without cleanup strategy
- Create excessive indexes (storage cost + write latency)
- Assume parent deletion cascades to subcollections
- Use transactions when batches suffice
---
**Related Skills**: `zod-firestore-type-safety`, `firebase-nextjs-integration-strategies`
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.