Firestore Schema Reference
Complete Firestore data models and collections for the TeamDay platform.
Collections Overview
TeamDay uses Firebase Firestore as its primary database. The schema follows a hierarchical structure with strong security rules and real-time subscriptions.
| Collection | Purpose | Access Control |
|---|---|---|
organizations | Organization/team management | Members only |
users | User account data | Owner only |
userProfiles | User preferences and settings | Owner only |
character | AI agents/assistants | Visibility-based |
spaces | Workspaces for agents | Visibility-based |
chats | Conversations | Owner/org members |
tasks | Agent-created tasks | Org members |
executions | Agent execution history | Org members |
missions | Long-running autonomous tasks | Org members |
scheduledTasks | Scheduled agent automations | Org members |
skills | Reusable prompt packages | Visibility-based |
subagents | Specialized agent configs | Visibility-based |
plugins | Configuration packages | Visibility-based |
pluginInstallations | Plugin installs per space | Org members |
mcpInstances | MCP server instances | Org members |
personal_access_tokens | API tokens | Owner only |
invites | Organization invites | Public read |
usageAggregates | Usage metrics | Org members |
creditTransactions | Credit balance history | Org members |
Core Collections
organizations
Organization/team management. Each user belongs to one or more organizations.
interface Organization {
id: string
name: string
logoUrl?: string
ownerId: string // User who created the org
members: string[] // Array of user IDs
subscription?: OrganizationSubscription
createdAt: Timestamp
updatedAt: Timestamp
// Encrypted fields (server-side only)
encryptedAnthropicApiKey?: string // Encrypted API key
}
Example Document:
{
"id": "org_abc123",
"name": "Acme Corporation",
"logoUrl": "https://storage.googleapis.com/...",
"ownerId": "user_xyz",
"members": ["user_xyz", "user_abc", "user_def"],
"subscription": {
"tier": "pro",
"status": "active"
},
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
Indexes Required:
members(array-contains) for member queriesownerIdfor owner queries
Subcollections:
gitCredentials/{credentialId}- OAuth tokens for Git integrations
users
User authentication and account data. Managed by Firebase Auth.
interface User {
id: string // Firebase Auth UID
email: string
displayName?: string
photoURL?: string
createdAt: Timestamp
lastSignIn?: Timestamp
}
Security: Owner-only access. No client-side deletion allowed.
Subcollections:
gitCredentials/{credentialId}- Personal Git OAuth tokens
userProfiles
User preferences, settings, and last-used organization.
interface UserProfile {
userId: string
lastUsedOrganizationId?: string
preferences?: {
theme?: 'light' | 'dark' | 'system'
locale?: string
}
createdAt: Timestamp
updatedAt?: Timestamp
// Encrypted fields (server-side only)
encryptedAnthropicApiKey?: string
encryptedClaudeCodeOAuthToken?: string
}
Security: Owner can read/update. Cannot modify encrypted credential fields from client.
character (AI Agents)
AI agents/assistants that users create and configure.
type CharacterVisibility = 'private' | 'organization' | 'public' | 'unlisted'
type CharacterCategory = 'marketing' | 'finance' | 'hr' | 'engineering' | 'operations' | 'general'
interface Character {
id: string
name: string
role: string // e.g., "Marketing Assistant"
initialGreeting: string
characterDescription: string
system_message: string // System prompt
systemPrompt?: string // Alias for system_message
image: string // Avatar URL
color: string // Brand color
// Configuration
model?: string // e.g., 'claude-sonnet-4-5-20250929'
visibility: CharacterVisibility
ownerId: string
organizationId?: string
ownerName?: string
// Resources
files: any[] // Attached files
linksToGetContextFrom: any[] // URLs for context
tools?: string[] | null // Legacy AI tools
advanced_tools?: string[] // MCP tool IDs
skillIds?: string[] // Skill references
subagentIds?: string[] // Subagent references
// Features
suggested_topics?: any[]
suggested_topics_conversation_starters?: string[]
voice?: string // Voice model ID
tags?: string[]
articles?: string[]
// Template system
isTemplate?: boolean // Global template flag
category?: CharacterCategory
featured?: boolean
version?: number
changelog?: string
templateId?: string // Source template ID
instanceCount?: number // For templates
// State
isForkable?: boolean
archived?: boolean
// Statistics
statistics?: {
likes: number
dislikes: number
uniqueChats: number
}
// Timestamps
createdAt: Date | Timestamp
updatedAt?: Date | Timestamp
// Soft delete
deletedAt?: Date | null
isDeleted?: boolean
}
Example Document:
{
"id": "char_123",
"name": "Marketing Maven",
"role": "Marketing Assistant",
"initialGreeting": "Hi! I'm here to help with marketing tasks.",
"characterDescription": "Expert in content marketing and social media",
"system_message": "You are a marketing specialist...",
"image": "https://storage.googleapis.com/avatars/marketing.png",
"color": "#FF6B6B",
"model": "claude-sonnet-4-5-20250929",
"visibility": "organization",
"ownerId": "user_xyz",
"organizationId": "org_abc123",
"skillIds": ["research-assistant", "technical-writer"],
"advanced_tools": ["mcp_google_analytics"],
"tags": ["marketing", "content"],
"archived": false,
"isTemplate": false,
"createdAt": "2024-01-15T10:00:00Z"
}
Indexes Required:
- Composite:
organizationId+isTemplate+createdAt(desc) ownerId+visibilityisTemplatefor template queries
Visibility Rules:
public: Visible to all authenticated usersorganization: Visible to all org membersprivate: Only owner can seeunlisted: Only owner can see (not in listings)- Templates (
isTemplate: true): Visible to all
spaces
Workspaces where agents operate. Each space has its own file system and git configuration.
type SpaceVisibility = 'private' | 'organization' | 'public'
type SpaceInitializationType = 'empty' | 'readme' | 'git' | 'starterKit'
type SpaceInitializationStatus = 'pending' | 'initializing' | 'ready' | 'failed'
interface Space {
id: string
organizationId: string
name: string
description: string
// Branding
coverImage?: string
gradientColors?: [string, string] // [fromColor, toColor]
coverImageAttribution?: {
source: 'unsplash' | 'upload' | 'url' | 'ai'
photographerName?: string
photographerUsername?: string
unsplashUrl?: string
}
// Access control
visibility: SpaceVisibility
ownerId: string
allowedUserIds?: string[] // For private spaces
// Configuration
assignedAgents?: string[] // Character IDs
instructions?: string // Fallback instructions
advanced_tools?: string[] // MCP instance IDs
skillIds?: string[] // Skill IDs
subagentIds?: string[] // Subagent IDs
pluginIds?: string[] // Plugin IDs to install
// Git configuration
git?: {
aiSettings?: {
autoCommitOnSessionEnd?: boolean
generateCommitMessages?: boolean
autoResolveConflicts?: boolean
requireApprovalForPush?: boolean
}
permissions?: Record<string, {
readers: string[]
writers: string[]
pushers: string[]
defaultPermission: 'none' | 'read' | 'write' | 'push'
}>
defaultUserId?: string
remotes?: Record<string, string> // Cache only
}
// Initialization
initializationStatus?: SpaceInitializationStatus
initializationType?: SpaceInitializationType
initializationError?: string
// Statistics
chatCount?: number
fileCount?: number
// Timestamps
createdAt: Date
updatedAt: Date
// Soft delete
deletedAt?: Date | null
isDeleted?: boolean
// Optimistic UI
isOptimistic?: boolean
clientSideId?: string
}
Example Document:
{
"id": "space_456",
"organizationId": "org_abc123",
"name": "Marketing Campaigns",
"description": "Space for marketing campaign work",
"visibility": "organization",
"ownerId": "user_xyz",
"assignedAgents": ["char_123"],
"skillIds": ["research-assistant"],
"gradientColors": ["#3b82f6", "#8b5cf6"],
"initializationStatus": "ready",
"initializationType": "empty",
"chatCount": 5,
"fileCount": 23,
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
Indexes Required:
organizationId+updatedAt(desc)ownerIdfor owner queriesvisibilityfor filtering
chats
Conversations between users and agents.
type ChatStatus = 'standby' | 'working' | 'done' | 'needs_attention' | 'error'
interface Chat {
id: string
organizationId: string
spaceId: string
title: string
subtitle?: string
status: ChatStatus
ownerId: string
lastMessage?: string
sessionId?: string // Claude Code session ID
// Session lifecycle
closed: boolean
closedAt?: Date | null
// Visibility
visibility?: 'private' | 'organization' | 'public'
// Timestamps
createdAt: Date
updatedAt: Date
}
Subcollections:
messages/{messageId}- Chat messages (immutable)
Messages Structure:
interface ChatMessage {
id: string
chatId: string
role: 'user' | 'assistant'
content: string
createdAt: Date
sessionId?: string
toolCalls?: Array<{
name: string
input: any
result?: string
}>
}
Indexes Required:
- Composite:
organizationId+spaceId+updatedAt(desc) ownerIdfor user's chatssessionIdfor session queries
Security: Messages are immutable (no update/delete).
tasks
Agent-created tasks for coordination and assignment.
type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'
interface Task {
id: string
organizationId: string
title: string
description: string
assignedTo?: string | null // Agent ID
priority: TaskPriority
status: TaskStatus
spaceId?: string | null
createdBy: string | null // Agent ID or user ID
// Timestamps
createdAt: string // ISO 8601
completedAt?: string | null
}
Indexes Required:
- Composite:
organizationId+status+createdAt(desc) assignedTofor assignment queriesspaceIdfor space tasks
executions
Agent execution tracking and delegation history.
type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
interface Execution {
id: string
organizationId: string
agentId: string
chatId: string
sessionId: string
status: ExecutionStatus
// Content
message: string // User's message
result?: string // Agent's response
error?: string // Error if failed
// Delegation tracking
parentExecutionId?: string
delegationDepth: number
// Timing
startedAt: Date
completedAt?: Date
duration?: number // milliseconds
// Cost tracking
tokensInput?: number
tokensOutput?: number
cost?: number
}
Indexes Required:
- Composite:
organizationId+chatId+startedAt(desc) sessionIdfor session queriesparentExecutionIdfor delegation chains
missions
Long-running autonomous agent tasks.
type MissionStatus = 'pending' | 'running' | 'paused' | 'completed' | 'failed'
type AgentType = 'claude' | 'gemini' | 'codex'
interface Mission {
id: string
userId: string
organizationId: string
spaceId?: string
title: string
goal: string
agentType: AgentType
// Schedule
schedule: {
type: 'once' | 'cron' | 'continuous'
value?: string // Cron expression
lastRun?: number
nextRun?: number
}
// State
state: {
currentStep: string
contextSummary: string
artifacts: string[]
memory: Record<string, any>
}
status: MissionStatus
logs: MissionLogEntry[]
// Timestamps
createdAt: number
updatedAt: number
}
interface MissionLogEntry {
timestamp: number
level: 'info' | 'warn' | 'error'
message: string
metadata?: any
}
Indexes Required:
- Composite:
organizationId+status+createdAt(desc) userIdfor user missionsspaceIdfor space missions
scheduledTasks
Scheduled agent automations (cron jobs).
interface ScheduledTask {
id: string
organizationId: string
characterId: string // Agent to run
spaceId?: string // Optional space context
// Task details
name: string
prompt: string // What to ask the agent
schedule: string // Cron expression
// State
isActive: boolean
lastRun?: Date | null
lastRunStatus?: 'success' | 'error' | 'running' | null
lastRunSessionId?: string | null
nextRun?: Date | null
// Metadata
createdAt: Date
createdBy: string
updatedAt?: Date | null
}
Related Collection: scheduledTaskExecutions/{executionId} stores execution history.
Indexes Required:
- Composite:
organizationId+isActive+nextRun(asc) characterIdfor agent schedulescreatedByfor creator queries
skills
Reusable prompt packages that extend agent capabilities.
type SkillVisibility = 'private' | 'organization' | 'public'
type SkillCategory = 'research' | 'writing' | 'data' | 'code' | 'marketing' | 'productivity' | 'file-handling' | 'other'
interface Skill {
id: string
// Identity
name: string // kebab-case (e.g., "research-assistant")
displayName: string // Human readable
description: string // When to invoke
// Content
prompt: string // SKILL.md markdown instructions
// Dependencies
builtInTools?: string[] // ["Read", "Write", "Bash", "WebSearch"]
requiredMCPs?: string[] // ["google-analytics", "bigquery"]
// Categorization
category: SkillCategory
tags?: string[]
icon?: string // Emoji or icon name
// Ownership
organizationId: string
ownerId: string
visibility: SkillVisibility
// Marketplace
featured?: boolean
verified?: boolean
usageCount?: number
rating?: number // 1-5
ratingCount?: number
// Versioning
version: number
changelog?: string
// Timestamps
createdAt: Date | Timestamp
updatedAt: Date | Timestamp
// Soft delete
isDeleted?: boolean
deletedAt?: Date | null
}
Example Document:
{
"id": "skill_research",
"name": "research-assistant",
"displayName": "Research Assistant",
"description": "Deep research on a topic using web search",
"prompt": "# Research Assistant\n\nYou are a research specialist...",
"category": "research",
"icon": "🔍",
"builtInTools": ["WebSearch", "WebFetch", "Read", "Write"],
"organizationId": "org_abc123",
"ownerId": "user_xyz",
"visibility": "organization",
"version": 1,
"createdAt": "2024-01-15T10:00:00Z"
}
Indexes Required:
- Composite:
organizationId+visibility+createdAt(desc) category+visibilityfor marketplacefeaturedfor featured skills
plugins
Configuration packages (GitHub repos) that bundle commands, agents, skills, and MCP servers.
type PluginVisibility = 'private' | 'organization' | 'public'
type PluginSourceType = 'github' | 'url' | 'path'
interface Plugin {
id: string
// Basic info
name: string // kebab-case identifier
displayName: string
description: string
icon?: string
// Source
source: {
source: PluginSourceType
repo?: string // owner/repo (GitHub)
url?: string // Git URL
path?: string // Local path
ref?: string // branch/tag/commit
}
// Manifest (cached from repo)
manifest?: {
name: string
description: string
version: string
author?: {
name: string
email?: string
url?: string
}
homepage?: string
repository?: string
license?: string
keywords?: string[]
category?: string
// Component flags
commands?: boolean | string[]
agents?: boolean | string[]
skills?: boolean | string[]
hooks?: boolean | string[]
mcpServers?: boolean | string[]
}
// Ownership
organizationId: string
ownerId: string
visibility: PluginVisibility
// Stats
installCount?: number
starCount?: number
// Verification
verified?: boolean
featured?: boolean
// Timestamps
createdAt: Date
updatedAt: Date
// Soft delete
isDeleted?: boolean
deletedAt?: Date | null
}
Related Collection: pluginInstallations/{installationId} tracks space installations.
Indexes Required:
- Composite:
organizationId+visibility+createdAt(desc) visibilityfor marketplacefeaturedfor featured plugins
personal_access_tokens
API tokens for programmatic access.
interface PersonalAccessToken {
id: string // Token ID (not the token itself)
uid: string // Owner user ID
organizationId: string
name: string // User-defined name
// Token hash (actual token never stored)
tokenHash: string
// Permissions
scopes: string[] // e.g., ["read:spaces", "write:chats"]
// Usage tracking
lastUsedAt?: Date | null
createdAt: Date
expiresAt?: Date | null
}
Security: Server-side managed. Users can only read/delete their own tokens.
Indexes Required:
- Composite:
uid+organizationId tokenHashfor authentication (unique)
invites
Organization invitation management.
interface Invite {
id: string
organizationId: string
email: string
role: 'member' | 'admin'
invitedBy: string // User ID
status: 'pending' | 'accepted' | 'expired'
expiresAt: Date
createdAt: Date
}
Security: Public read (needed for invite links). Only org owners can create/update/delete.
Server-Side Only Collections
These collections have no client access and are managed exclusively via Admin SDK:
authStates
Authentication tier caching for performance.
cli_pending_auth
Temporary OAuth codes for CLI authentication.
cli_refresh_tokens
Refresh tokens for CLI tool.
partner_connections
RSA keys for partner authentication (encrypted private keys).
partner_organizations
Links partners to organizations.
usageLogs
Detailed per-request usage logs.
Read-Only Collections (Client)
usageAggregates
Aggregated usage metrics. Structure: usageAggregates/{period}/data/{orgId}_{dateKey}
interface UsageAggregate {
organizationId: string
period: 'daily' | 'monthly'
dateKey: string // YYYY-MM-DD
// Metrics
totalRequests: number
totalTokensInput: number
totalTokensOutput: number
totalCost: number
// Breakdowns
byAgent: Record<string, {
requests: number
tokensInput: number
tokensOutput: number
cost: number
}>
}
Security: Org members can read. Writing is server-side only.
creditTransactions
Credit balance history.
interface CreditTransaction {
id: string
organizationId: string
type: 'purchase' | 'usage' | 'refund' | 'grant'
amount: number // Positive or negative
balance: number // Running balance
description: string
metadata?: any
createdAt: Date
}
Security: Org members can read. Writing is server-side only.
Indexes Summary
Required Composite Indexes
// Organizations
organizations: members (array-contains)
// Characters
character: organizationId + isTemplate + createdAt (desc)
character: ownerId + visibility
// Spaces
spaces: organizationId + updatedAt (desc)
// Chats
chats: organizationId + spaceId + updatedAt (desc)
// Tasks
tasks: organizationId + status + createdAt (desc)
// Executions
executions: organizationId + chatId + startedAt (desc)
// Missions
missions: organizationId + status + createdAt (desc)
// Scheduled Tasks
scheduledTasks: organizationId + isActive + nextRun (asc)
// Skills
skills: organizationId + visibility + createdAt (desc)
skills: category + visibility
// Plugins
plugins: organizationId + visibility + createdAt (desc)
// Personal Access Tokens
personal_access_tokens: uid + organizationId
Single-Field Indexes
Most createdAt, updatedAt, ownerId, visibility fields are auto-indexed by Firestore.
Security Rules Summary
Access Patterns
Owner-only:
users- Only owner can read/updateuserProfiles- Only owner can read/updatepersonal_access_tokens- Owner can read/delete
Org Member Access:
organizations- Members can readspaces- Filtered by visibilitychats- Owner or org memberstasks- Org membersexecutions- Org membersmissions- Org membersscheduledTasks- Org members
Visibility-Based:
character- Public/org/private/unlistedskills- Public/org/privateplugins- Public/org/private
Server-Only:
authStatescli_pending_authcli_refresh_tokenspartner_connectionspartner_organizationsusageLogs
Read-Only (Client):
usageAggregates- Org members readcreditTransactions- Org members read
Encrypted Fields
These fields are encrypted server-side and cannot be modified from client:
organizations.encryptedAnthropicApiKeyuserProfiles.encryptedAnthropicApiKeyuserProfiles.encryptedClaudeCodeOAuthToken
Client updates attempting to modify these fields will be rejected by security rules.
Best Practices
Firestore Constraints
- No Undefined Values: Firestore doesn't allow
undefined. Usenullor omit the field.
// ❌ Bad
await updateDoc(docRef, { optional: undefined })
// ✅ Good
await updateDoc(docRef, { optional: value ?? null })
// ✅ Better - omit undefined
const cleanData = Object.fromEntries(
Object.entries(data).filter(([_, v]) => v !== undefined)
)
- Use Server Timestamps: Always use
serverTimestamp()for consistency.
import { serverTimestamp } from 'firebase/firestore'
await addDoc(collection, {
...data,
createdAt: serverTimestamp()
})
- Cleanup Subscriptions: Always unsubscribe from real-time listeners.
const unsubscribe = onSnapshot(query, snapshot => { })
onUnmounted(() => {
if (unsubscribe) unsubscribe()
})
Soft Deletes
Most collections use soft deletes for recoverability:
// Soft delete
await updateDoc(docRef, {
isDeleted: true,
deletedAt: new Date()
})
// Restore
await updateDoc(docRef, {
isDeleted: false,
deletedAt: null
})
Optimistic Updates
For better UX, the app uses optimistic updates for creates:
// Add optimistic item immediately
optimisticItems.value.push(newItem)
// Create in Firestore
const docRef = await addDoc(collection, data)
// Snapshot listener updates with real data
Migration Notes
Legacy Fields
Some collections have deprecated fields maintained for backward compatibility:
Characters:
systemPrompt→ Usesystem_messageskills(inline) → UseskillIds(references)tools→ Useadvanced_tools
UserProfiles:
anthropicApiKey(plaintext) → UseencryptedAnthropicApiKeyclaudeCodeOAuthToken(plaintext) → UseencryptedClaudeCodeOAuthToken
Data Migration
When migrating data, always:
- Create new fields alongside old ones
- Update application code to read from both
- Background job migrates data
- Remove old field references after migration complete
- Keep deprecated fields documented for 6 months minimum