Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

Firestore Security Rules Reference

Documents every Firestore and Storage security rule in firestore.rules and storage.rules. These rules enforce tenant isolation, RBAC, and data integrity at the database level.

Overview

PropertyValue
Rules version2
Firestore rules filefirestore.rules (~3,751 lines)
Storage rules filestorage.rules (~116 lines)
Multi-tenancy modelorganizationId field on every document + custom claims
Auth mechanismFirebase Auth custom claims (role, organizationId, isSuperAdmin)
Role hierarchySuperAdmin > Admin > Manager > User
All rules follow a strict tenant-isolation pattern: every read/write must validate the user’s organizationId against the document’s organizationId. Cross-tenant access is never allowed except for SuperAdmin.

Helper Functions

Authentication & Identity

FunctionPurposeUsed By
isAuthenticated()Returns true if request.auth != nullNearly all rules
userId()Returns request.auth.uidOwner checks, creator validation
getUserOrganizationId()Resolves org ID from auth claims, then falls back to employees or people document lookupOrg membership checks
employeeDocPath()Returns Firestore path /employees/{uid}Role/org lookups
personDocPath()Returns Firestore path /people/{uid}Role/org lookups (modern collection)

Organization Membership

FunctionPurposeUsed By
hasOrgClaim(orgId)Checks organizationId or organizationIds (array) in auth claimsbelongsToOrg()
belongsToOrg(orgId)Comprehensive org membership check: impersonation, claims, employee/people doc, or SuperAdminMost collection rules
hasEmployeeDoc(orgId)Checks if employees/{uid} exists and matches orgbelongsToOrg()
hasPeopleDoc(orgId)Checks if people/{uid} exists and matches orgbelongsToOrg()
isOrgActive(orgId)Validates organizations/{orgId}.status == 'active'Create rules (prevents writes to suspended orgs)

Role Checks

FunctionPurposeUsed By
hasAnyRoleClaim(roles)Checks if auth claim role is in the given arrayRole hierarchy functions
employeeSystemRoleIn(roles)Checks employees/{uid}.systemRole against role arrayStored role fallback
personSystemRoleIn(roles)Checks people/{uid}.systemRole against role arrayStored role fallback
hasStoredSystemRole(roles)Combines employeeSystemRoleIn + personSystemRoleInRole hierarchy functions
isSuperAdmin()Checks isSuperAdmin claim, or role claim/stored role matches superadmin/Super Admin/SuperAdminGlobal admin access
isAdmin()SuperAdmin OR admin/Admin claim/stored roleOrg-level admin access
isManagerOrHigher()SuperAdmin OR admin/manager claim/stored roleApproval workflows

Impersonation

FunctionPurposeUsed By
isImpersonating()Checks request.auth.token.impersonating == true AND isSuperAdmin()belongsToOrg()
impersonatingOrgId()Returns request.auth.token.impersonatingOrgIdImpersonation org context

Auditor Access

FunctionPurposeUsed By
hasAuditorAccess(orgId)Checks auditorAccessGrants/{uid}_{orgId} for active, non-expired grantisAuditor()
isAuditor(orgId)SuperAdmin OR (auditor role + active access grant)Compliance/audit collections

Resource Checks

FunctionPurposeUsed By
isOwner(resource)resource.data.userId == userId()Owner-only operations
isLocked(resource)resource.data.isLocked == trueJournal/timesheet approval lock
canApproveJournals()Alias for isManagerOrHigher()Journal approval rules

Permission Checks

FunctionPurposeUsed By
hasUserPermission(orgId, uid, permission)Looks up employee’s roleId, then checks roles/{roleId}.permissions array for the given permission stringAPI keys, contacts, interactions

Collection-scoped Helpers

These are defined inside specific match blocks:
FunctionScopePurpose
hasApiKeyPermission(orgId)apiKeys/{keyId}Checks SuperAdmin, Admin, or MANAGE_API_KEYS/MANAGE_PLATFORM permission
hasContactPermission(permission)contacts/{contactId}Checks SuperAdmin, Admin, or specific permission
hasInteractionPermission(permission)interactions/{interactionId}Checks SuperAdmin, Admin, or specific permission
hasSegmentPermission(permission)contactSegments/{segmentId}Checks SuperAdmin, Admin, or specific permission

Collection Rules

Organizations

organizations/{orgId}

  • read: belongsToOrg(orgId)
  • create: isSuperAdmin() (tenant provisioning only)
  • update: Admin or SuperAdmin or impersonating; validates partnerOrgId reference if present
  • delete: isSuperAdmin() (soft delete via status change)

organizations/{orgId}/roles/{roleId}

  • read: belongsToOrg(orgId)
  • write: SuperAdmin or Admin

organizations/{orgId}/config/{configId}

  • read: belongsToOrg(orgId)
  • write: SuperAdmin or Admin

organizations/{orgId}/integration_exports/{exportId}

  • read: belongsToOrg(orgId)
  • write: Admin or SuperAdmin

organizations/{orgId}/api_keys/{keyId}

  • read: Admin only
  • write: Admin or SuperAdmin

organizations/{orgId}/webhooks/{webhookId}

  • read: Admin only
  • write: Admin or SuperAdmin

organizations/{orgId}/widgetOverrides/{widgetId}

  • read: belongsToOrg(orgId)
  • create/update: Manager+
  • delete: Admin only

organizations/{orgId}/active_timers/{employeeId}

  • read: Org member; only timer owner or Manager+
  • create/update: Timer owner only
  • delete: Timer owner or Admin

organizations/{orgId}/agent_runs/{runId}

  • read: belongsToOrg(orgId)
  • create/update: Admin or SuperAdmin
  • delete: SuperAdmin only

organizations/{orgId}/credit_reservations/{reservationId}

  • read: belongsToOrg(orgId)
  • create/update: Admin or SuperAdmin
  • delete: SuperAdmin only

organizations/{orgId}/moduleInstallations/{moduleId}

  • read: belongsToOrg(orgId)
  • create/update: Admin or SuperAdmin
  • delete: SuperAdmin only

organizations/{orgId}/modules/{moduleId}/{document=**}

  • read: belongsToOrg(orgId)
  • write: belongsToOrg(orgId) (module-specific permissions enforced at app layer)

organizations/{orgId}/complianceSummaries/{summaryId}

  • read: belongsToOrg(orgId)
  • create/update: belongsToOrg(orgId)
  • delete: Admin or SuperAdmin

organizations/{orgId}/submissionMetrics/{metricId}

  • read: belongsToOrg(orgId)
  • create/update: belongsToOrg(orgId)
  • delete: Admin or SuperAdmin

organizations/{orgId}/complianceAlerts/{alertId}

  • read: belongsToOrg(orgId)
  • create/update: belongsToOrg(orgId)
  • delete: Admin or SuperAdmin

organizations/{orgId}/reportTemplates/{templateId}

  • read/list: belongsToOrg(orgId)
  • create/update/delete: Manager+ and belongsToOrg(orgId)

organizations/{orgId}/intelligenceCache/{docId}

  • read: belongsToOrg(orgId)
  • write: Denied (Cloud Functions only via Admin SDK)

People & Employees

{peopleCollection}/{employeeId} (where peopleCollection in ['people', 'employees'])

  • read: Own document, SuperAdmin, or org member
  • create: SuperAdmin, Admin, or authenticated user accepting invitation (validated via invitations/{invitationId})
  • update: SuperAdmin, Admin (full access), owner (cannot change organizationId, systemRole, bsn, bankAccount), or Manager (cannot change bsn, bankAccount)
  • delete: SuperAdmin or Admin
  • Notes: BSN and bank account are encrypted at storage level; systemRole and organizationId are immutable for self-service updates

Projects

projects/{projectId}

  • read: Authenticated + belongsToOrg(resource.data.organizationId)
  • create: SuperAdmin or Manager+ (org must be active)
  • update: SuperAdmin or Manager+ (cannot change organizationId)
  • delete: SuperAdmin or Admin

projects/{projectId}/phase_history/{transitionId}

  • read: Authenticated + belongs to project’s org
  • create: Manager+ in project’s org; requires fromPhase, toPhase, transitionedAt, transitionedBy fields
  • update: Never (immutable)
  • delete: Never (immutable audit trail)

Journals / Timesheets

journals/{journalId} (two match blocks exist — one with userId, one with employeeId)

  • read: Authenticated + org member
  • create: SuperAdmin or owner creating own entry (org must be active)
  • update: SuperAdmin, owner (if not locked, cannot change org), or Manager+ for approval
  • delete: SuperAdmin, owner (if not locked), or Admin

timesheets/{journalId} (legacy alias)

  • Same pattern as journals; supports both userId and employeeId fields for backward compatibility

journalSubmissions/{retroId} (Monthly Journal Submissions)

  • read: Owner or Manager+
  • create: Employee creating own submission in org
  • update: Owner (if status is Draft) or Manager+ for approval
  • delete: Owner (if Draft) or Admin

journalSubmissions/{retroId}/history/{historyId}

  • read: Submission owner or Manager+
  • create: Org member who is the submission owner or Manager+

journalGenerationJobs/{jobId}

  • read: Owner or Manager+
  • create: Owner in org
  • update: Owner (cannot change employeeId/organizationId) or Manager+
  • delete: Owner or Admin

Leave Management

leave-balances/{balanceId} (appears twice — merged rules)

  • get: Authenticated; own balance or org member (handles resource == null for non-existent docs)
  • list: Authenticated + belongs to own org
  • create/update: Manager+ or system; cannot change employeeId
  • delete: Admin only

Expenses

expenses/{expenseId}

  • read: Authenticated + org member
  • create: SuperAdmin or owner creating own expense (org must be active)
  • update: SuperAdmin, owner (if status != approved, cannot change org), or Manager+ for approval
  • delete: SuperAdmin, owner (if not approved), or Admin

expenseAllocations/{allocationId}

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Admin only

Deadlines / Milestones

{milestoneCollection}/{deadlineId} (where milestoneCollection in ['milestones', 'deadlines'])

  • read: Authenticated + org member or Auditor
  • create: SuperAdmin, Manager+ (org active), or system-generated (createdBy == 'system')
  • update: SuperAdmin, Manager+, assignee (if in assignedTo array), or system
  • delete: SuperAdmin, Admin, or creator of custom deadlines

Compliance & Rule Engine

compliance_rules/{ruleId}

  • read: Authenticated; platform defaults readable by all, org-scoped rules by org members or auditors
  • create: SuperAdmin or Manager+ in org (org active)
  • update: SuperAdmin or Manager+ (cannot change organizationId)
  • delete: SuperAdmin or Admin

rule_templates/{templateId}

  • read: Authenticated; public templates by all, org-scoped by org members
  • create: SuperAdmin or Manager+
  • update: SuperAdmin or Manager+ (org templates only, cannot change org)
  • delete: SuperAdmin (public) or Admin (org templates)

grant_rules/{junctionId}

  • read: Authenticated + org member
  • create: SuperAdmin or Manager+ (org active)
  • update: SuperAdmin or Manager+ (cannot change org)
  • delete: SuperAdmin or Admin

rule_violations/{violationId}

  • read: Manager+ or Auditor
  • create: SuperAdmin, Manager+ in org, or system
  • update: SuperAdmin or Manager+ (cannot change org)
  • delete: SuperAdmin or Admin

rule_analytics/{metricsId}

  • read: Authenticated + Manager+
  • create/update: SuperAdmin or system (createdBy == 'system')
  • delete: SuperAdmin only

platform_rules/{ruleId}

  • read: Any authenticated user
  • create/update/delete: SuperAdmin only
  • Notes: Platform-wide resource, no organizationId

tenant_donor_configs/{orgId}

  • read: Org member or Auditor
  • create: SuperAdmin or Manager+ (doc ID must match org ID)
  • update: SuperAdmin or Manager+ (cannot change org)
  • delete: SuperAdmin only

complianceSummaries/{summaryId} (top-level)

  • read: Authenticated + org member
  • create/update: SuperAdmin or authenticated
  • delete: SuperAdmin only

compliance_summaries/{summaryId} (snake_case variant)

  • Same as above

compliance_alerts/{alertId} / compliance-alerts/{alertId} / complianceAlerts/{alertId}

  • read: Authenticated + org member
  • create/update: Authenticated + org member (for acknowledgment)
  • delete: Admin or SuperAdmin
  • Notes: Three naming variants exist for backward compatibility

complianceRequirements/{requirementId}

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Admin in org

compliance-rules/{ruleId} (hyphenated, RAG-extracted)

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Admin in org

compliance-tasks/{taskId}

  • read: Org member; task assignee or Manager+
  • create: Manager+ or system
  • update: Org member; assignee or Manager+
  • delete: Admin only

platform-compliance-rules/{ruleId}

  • read: Any authenticated user
  • create/update/delete: SuperAdmin only

Invitations

invitations/{invitationId}

  • read: Invitee (by email match) or Admin
  • create: Admin (org must be active)
  • update: Invitee (accepting) or Admin
  • delete: Admin

Audit & Security

auditLogs/{logId}

  • read: SuperAdmin, Admin, or Auditor
  • create: SuperAdmin or any authenticated user
  • update/delete: Never (immutable)

audit_logs/{logId} (snake_case variant)

  • read: SuperAdmin or Admin in org
  • create: Any authenticated user
  • update/delete: Never (immutable)

securityEvents/{eventId}

  • read: SuperAdmin, Admin of org, or event owner
  • create: true (allows pre-auth logging)
  • update/delete: Never (immutable)

securityAlertConfigs/{configId}

  • read: true (needed for login threshold checks)
  • create/update: SuperAdmin only
  • delete: SuperAdmin only

loginAttempts/{attemptId}

  • read: SuperAdmin or own attempts
  • create: true (pre-auth)
  • update/delete: Never (immutable)

userSessions/{sessionId}

  • read: SuperAdmin or own sessions
  • create: Authenticated, own session
  • update: SuperAdmin or own session
  • delete: SuperAdmin or own session

imuserationSessions/{sessionId} (Impersonation Sessions)

  • read: SuperAdmin who created the session
  • create: SuperAdmin, must set own superAdminId
  • update: SuperAdmin who owns the session
  • delete: Never (audit trail)

auditorAccessGrants/{grantId}

  • read: Admin, active auditor, or SuperAdmin
  • create/update: Admin only
  • delete: Never (audit trail)

configurationSnapshots/{snapshotId}

  • read: Auditor, Admin, or SuperAdmin
  • create: Auditor or Admin, must set createdBy to own UID
  • update/delete: Never (immutable)

auditExportJobs/{jobId}

  • read: Requester, Auditor, Admin, or SuperAdmin
  • create: Auditor or Admin, must set requestedBy to own UID
  • update: Never (server-side only)
  • delete: Never (audit trail)

securityPolicies/{policyId}

  • read: Admin (for org_* policies) or SuperAdmin
  • create/update: Admin (for org_*) or SuperAdmin (for global)
  • delete: SuperAdmin only

Onboarding

onboardingChecklists/{checklistId}

  • read: User (for user_* checklists) or Admin (for org_*)
  • create: SuperAdmin (for org_*) or user (for user_*)
  • update: User (own user_* checklist) or Admin (org_*)
  • delete: Never

Team & Role Management

teamMemberRequests/{requestId}

  • list: Any authenticated user
  • get: Request creator or Admin
  • create: Manager+; must set requestedBy to own UID, status = pending, org must be active
  • update: Admin only; preserves immutable fields (requestedBy, proposedName, proposedEmail, proposedRole)
  • delete: Never

roleChangeRecommendations/{recommendationId}

  • list: Any authenticated user
  • get: Recommender, affected employee, or Admin
  • create: Manager+; validates employee exists, status = pending
  • update: Admin only; preserves immutable fields
  • delete: Never

Referrals & Gamification

referrals/{referralId}

  • get: SuperAdmin or org member
  • list: Any authenticated user
  • create: SuperAdmin or authenticated org member
  • update/delete: SuperAdmin or Admin

missionCredits/{userId}

  • get: Own wallet or SuperAdmin
  • list: Any authenticated user (leaderboard)
  • create/update: SuperAdmin only
  • delete: Never

creditTransactions/{transactionId}

  • read/list: Own transactions or SuperAdmin / any authenticated
  • create: SuperAdmin only
  • update/delete: Never (immutable)

creditRedemptions/{redemptionId}

  • read/list: Own redemptions, Admin of org, or SuperAdmin
  • create: Authenticated user; own userId, status = pending
  • update/delete: SuperAdmin only

badges/{badgeId}

  • read/list: Own badges, Admin of org, or SuperAdmin
  • create/update: Never (server-side only)
  • delete: Never

gamificationPreferences/{userId}

  • read: Own or Admin/SuperAdmin
  • create/update: Own only
  • delete: Own or SuperAdmin

campaigns/{campaignId}

  • read/list: Any authenticated user
  • create/update: Admin or SuperAdmin
  • delete: SuperAdmin only

Notifications

notifications/{notificationId}

  • read/list: SuperAdmin (all), recipient, or platform-wide (recipientId == 'all')
  • create: SuperAdmin or authenticated user (must include organizationId)
  • update: SuperAdmin or recipient (marking as read)
  • delete: SuperAdmin, recipient, or Admin of org

fcmTokens/{tokenId}

  • read/list: SuperAdmin, token owner, or authenticated
  • create: Authenticated; own employeeId, must include organizationId
  • update: Own token or SuperAdmin; cannot change employeeId
  • delete: Own token or SuperAdmin

notificationDeliveries/{deliveryId}

  • read/list: SuperAdmin, recipient, or authenticated
  • create: Authenticated; must include organizationId and recipientId
  • update/delete: SuperAdmin only

Configuration

config/{configId}

  • read: Any authenticated user
  • create/update/delete: SuperAdmin only

platformConfig/{configId}

  • read: true (unauthenticated — needed for login page branding)
  • create/update: SuperAdmin or Admin
  • delete: Never

Donors / Foundations

foundations/{foundationId}

  • read: Any authenticated user
  • create: SuperAdmin or authenticated user (sets createdBy)
  • update: SuperAdmin or any authenticated user
  • delete: SuperAdmin only

Grant Management

grantOpportunities/{opportunityId}

  • read: Any authenticated user
  • create/update/delete: Admin or SuperAdmin

{trackerCollection}/{pipelineId} (where trackerCollection in ['grantTracker', 'grantPipeline'])

  • read: Authenticated + org member
  • create: Authenticated + org member
  • update: Assigned team member (in assignedTo) or Manager+
  • delete: Admin + org member
Subcollections: tasks/{taskId}, documents/{documentId}
  • read/write: Authenticated + belongs to parent pipeline’s org
Subcollection: activities/{activityId}
  • read: Authenticated + belongs to parent pipeline’s org
  • create: Same
  • update/delete: Never (immutable)

grantApplications/{applicationId}

  • read: Authenticated + org member
  • create: Authenticated + org member
  • update: Creator or Manager+
  • delete: Admin + org member

activeGrants/{grantId}

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Admin in org

Risk Module

risks/{riskId}, riskMitigations/{mitigationId}, riskEscalations/{escalationId}

  • read: Authenticated + org member
  • create: Authenticated + org member (org active) + schema validation function
  • update: Authenticated + org member (cannot change org) + schema validation
  • delete: Admin + org member
  • Notes: Reference validation functions (isValidRisksDoc, etc.) that enforce schema at the rules level

Marketing Module

marketingPages/{pageId}, mediaAssets/{assetId}, publishingSchedules/{scheduleId}

  • Standard org-scoped pattern: read by org members, create/update by org members with validation, delete by Admin

Referral Module (Program/Tracking/Rewards)

referralPrograms/{programId}, referralTracking/{trackingId}, referralRewards/{rewardId}

  • Standard org-scoped pattern with validation functions

Support Module

tutorials/{tutorialId}, kbArticles/{articleId}, supportVideos/{videoId}

  • Standard org-scoped pattern with validation functions

Pending Signups

pendingSignups/{email}

  • read: Never
  • create: Anyone (requires referralCode + timestamp fields)
  • update/delete: Never (Cloud Functions process and clean up)

API Keys & Activity Feeds

apiKeys/{keyId}

  • read: Org member + API key permission
  • list: SuperAdmin, Admin, or MANAGE_API_KEYS permission
  • create: Org member + API key permission
  • update: Same + cannot change organizationId
  • delete: Org member + API key permission

activityFeeds/{employeeId}/monthly/{monthId}

  • read: SuperAdmin, own data, or Admin in org
  • create/update: SuperAdmin, own data, or authenticated with org
  • delete: SuperAdmin, own data, or Admin in org

HR Administration

cao-rules/{ruleId}

  • read: Authenticated + org member
  • create/update/delete: Admin only

payroll-exports/{exportId}

  • read: Admin + org member (sensitive salary data)
  • create/update/delete: Admin only (update preserves org)

Widgets

widgets/{widgetId}, widgetAssignments/{assignmentId}

  • read/create/update/delete: true (open for development/seeding)
  • Notes: These are intentionally permissive for emulator development

platform/widgets/definitions/{widgetId}, platform/widgets/assignments/{assignmentId}

  • read: Any authenticated user
  • create/update/delete: SuperAdmin only

platform/modules/definitions/{moduleId}

  • read: Any authenticated user (marketplace browsing)
  • create/update/delete: SuperAdmin only

RAG System

document-chunks/{chunkId}

  • read: Authenticated + org member
  • write: Never (Cloud Functions only via Admin SDK)

processed-documents/{docId}

  • read: Authenticated + org member
  • create: Authenticated + org member
  • update: Authenticated + org member (cannot change org)
  • delete: Manager+ in org

ai-query-logs/{logId}

  • read: Authenticated + org member; own logs or Manager+
  • write: Never (Cloud Functions only)

query-cache/{cacheId}

  • read: Authenticated + org member
  • write: Never (Cloud Functions only)

activity-rules/{ruleId}, project-deadlines/{deadlineId}

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Admin in org

Partner Member Benefit Program

partnerOrganizations/{partnerId}

  • read: SuperAdmin only
  • create: SuperAdmin; must set createdBy to own UID
  • update: SuperAdmin; cannot change createdBy
  • delete: Never (soft delete via status)

memberInvitations/{invitationId}

  • read: SuperAdmin or any authenticated user (for invitation code validation)
  • list: SuperAdmin only (prevents enumeration)
  • create: SuperAdmin; validates partner org exists
  • update: SuperAdmin or authenticated user (limited to status/timestamp updates, core fields immutable)
  • delete: Never (audit trail)

partnershipMetrics/{metricId}

  • read: SuperAdmin only
  • write: Never (Cloud Functions only)

Reporting

schedules/{scheduleId}

  • read: Authenticated + org member
  • create/update/delete: Manager+ in org

reportTemplates/{templateId} (top-level)

  • read: Authenticated + org member
  • list: Any authenticated user
  • create/update/delete: Manager+ in org

NGO HR & Staff Allocation

volunteers/{volunteerId}

  • read: Org members
  • create/update: Manager+ (own org or SuperAdmin)
  • delete: Admin only

staffAllocations/{allocationId}

  • read: Org member; Manager+ or own allocation
  • write: Never (computed server-side)

payrollExports/{exportId} (camelCase variant)

  • read: Admin or SuperAdmin in org
  • create: Admin in org or SuperAdmin
  • update: Admin (cannot change org)
  • delete: Admin (draft exports only)

accrualBalances/{balanceId}

  • read: Admin or own balance in org
  • write: Never (computed server-side)

Relations Module (CRM)

contacts/{contactId}

  • read: Org member + Manager+ or VIEW_RELATIONS permission
  • create: CREATE_CONTACT permission; validates required fields, createdBy/updatedBy must match caller
  • update: EDIT_CONTACT permission; cannot change org, createdAt, createdBy; must set updatedBy
  • delete: DELETE_CONTACT permission + org member

interactions/{interactionId}

  • read: Org member + Manager+ or VIEW_RELATIONS permission
  • create: Manager+ or CREATE_CONTACT permission; validates required fields
  • update: Manager+ or EDIT_CONTACT permission; cannot change org, contact, or immutable audit fields
  • delete: Manager+ or DELETE_CONTACT permission + org member

contactViews/{viewId}

  • read: Org member + VIEW_RELATIONS permission; own views or shared views
  • create: CREATE_CONTACT permission; enforces owner
  • update: Own views + EDIT_CONTACT permission
  • delete: Own views or Admin

contactSegments/{segmentId}

  • read: Org member + Manager+ or VIEW_RELATIONS
  • create/update/delete: Manager+ or MANAGE_CONTACT_SEGMENTS or relevant CRUD permission

HubSpot Integration

hubspotConfigs/{organizationId}

  • read: Admin of org or SuperAdmin
  • create: Admin; doc ID must match org ID; validates required fields (tokens, portal ID, sync settings)
  • update: Admin; cannot change org
  • delete: Admin of org or SuperAdmin

hubspotSyncLogs/{logId}

  • read: Admin or VIEW_RELATIONS permission + org member
  • create: Admin; validates extensive required fields
  • update: Never (immutable audit log)
  • delete: Admin + org member

Cross-Org Intelligence Platform

All under platform/intelligence/ path:

platform/intelligence/consent/{orgId}

  • read/create/update: Admin of the org
  • delete: Never

platform/intelligence/snapshots/{snapshotId}

  • read: SuperAdmin only
  • write: Never (Cloud Functions only)

platform/intelligence/benchmarks/{benchmarkId}, platform/intelligence/funderProfiles/{funderType}, platform/intelligence/trends/{trendId}

  • read: Authenticated user whose org has opted in (consent/{orgId}.optedIn == true)
  • write: Never (Cloud Functions only)

platform/intelligence/predictions/{predictionId}

  • read: SuperAdmin only
  • write: Never (Cloud Functions only)

Document Library

document-library/{documentId}

  • read: Authenticated + org member
  • create: Authenticated; own org, org must be active
  • update: Org member; creator or Admin; cannot change org
  • delete: Admin + org member

document-folders/{folderId}

  • read: Authenticated + org member
  • create: Manager+ in org
  • update: Creator or Admin; cannot change org
  • delete: Admin only

document-templates/{templateId}

  • read: Authenticated; platform-scoped or org member
  • create: Admin; scope must be organization
  • update/delete: Admin + org member

Stakeholder Portal

portalTokens/{tokenId}

  • read: SuperAdmin or Manager+ in org
  • create: Manager+ in org; sets createdBy, isActive = true
  • update: Manager+ in org; cannot change organizationId or projectId
  • delete: Never (soft delete via isActive = false)

portalSessions/{sessionId}

  • read: SuperAdmin or Admin in org
  • create/update: Never (Cloud Function only)
  • delete: Never (audit trail)

portalComments/{commentId}

  • read: SuperAdmin or org member
  • create: Authenticated org member; authorType must be organization, authorId must be caller
  • update: Author (editing) or org member marking stakeholder comments as read
  • delete: Admin + org member

portalReportAcknowledgments/{ackId}

  • read: Org member
  • create: Never (Cloud Function only)
  • update: Manager+ in org
  • delete: Never

portalSubmissions/{submissionId}

  • read: Org member
  • create: Never (Cloud Function only)
  • update: Manager+ in org (review)
  • delete: Never (audit trail)

Grantor Relationship Management

grantorInteractions/{interactionId}

  • read: Org member or Auditor
  • create: Authenticated + org member
  • update: Creator or Manager+ in org
  • delete: Admin + org member

reportingDeadlines/{deadlineId}

  • read: Org member or Auditor
  • create: Authenticated + org member
  • update: Creator, assignee, or Manager+
  • delete: Admin, or creator (if not completed)

M&E (Monitoring & Evaluation)

meIndicators/{indicatorId}

  • read: Authenticated + org member
  • create: SuperAdmin or authenticated org member (org active)
  • update: SuperAdmin or creator/Manager+ (cannot change org)
  • delete: Admin + org member

meIndicatorTargets/{targetId}, meDataPoints/{dataPointId}

  • read: Authenticated + org member
  • create: SuperAdmin or authenticated org member (org active)
  • update: SuperAdmin or creator/Manager+ (cannot change org)
  • delete: Creator or Manager+

meDashboardConfigs/{configId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

meKoboConnections/{connectionId}

  • read: Authenticated + org member
  • create/update/delete: Admin only (stores API tokens)

meKoboFormLinks/{formLinkId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Creator or Manager+

meIndicatorSummaries/{summaryId}

  • read: Authenticated + org member
  • write: Never (Cloud Functions only)

meGrantIndicatorConfigs/{configId}

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Creator or Manager+

meAISuggestions/{suggestionId}

  • read: Authenticated + org member
  • create/update: Manager+ in org
  • delete: Never (audit trail)

meWorkflowRules/{ruleId}

  • read: Authenticated + org member
  • create/update/delete: Admin in org

meWorkflowExecutionLogs/{logId}

  • read: Authenticated + org member
  • write: Never (Cloud Functions only)

Grant Calendar Extension

grantCalendarEvents/{eventId}

  • read: Authenticated + org member
  • create: SuperAdmin or authenticated org member (org active)
  • update: SuperAdmin or creator/Manager+ (cannot change org)
  • delete: Admin + org member

grantCalendarSettings/{settingsId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

Donor Wallet Extension

donorWallets/{walletId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

walletTransactions/{txId}

  • read: Authenticated + org member
  • create: SuperAdmin or authenticated org member (org active)
  • update: SuperAdmin or Manager+ (cannot change org)
  • delete: Admin + org member

walletAllocations/{allocId}

  • read: Authenticated + org member
  • create: SuperAdmin or authenticated org member (org active)
  • update: SuperAdmin or creator/Manager+
  • delete: Admin + org member

ngoDirectory/{ngoId}, donorWalletSettings/{settingsId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

matchingPrograms/{programId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Admin in org
  • delete: Admin + org member

Virtual Giving Card Extension

virtualCards/{cardId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

cardTransactions/{txId}

  • read: Authenticated + org member
  • create: SuperAdmin or authenticated org member (org active)
  • update: SuperAdmin or Manager+ (cannot change org)
  • delete: Admin + org member

cardSettings/{settingsId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

Corporate CSR Hub Extension

corporateAccounts/{accountId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Admin in org
  • delete: SuperAdmin only

employeeDonations/{donationId}

  • read: Authenticated + org member
  • create: Authenticated org member (org active)
  • update: SuperAdmin or Manager+ (cannot change org)
  • delete: Admin + org member

matchingTransactions/{txId}, matchingRules/{ruleId}

  • read: Authenticated + org member
  • create: SuperAdmin or Admin/Manager+ in org
  • update: SuperAdmin or Admin (cannot change org)
  • delete: Admin + org member

csrCampaigns/{campaignId}, csrImpactReports/{reportId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Manager+ in org
  • delete: Admin + org member

ergGroups/{groupId}

  • read: Authenticated + org member
  • create: Authenticated org member (org active)
  • update: SuperAdmin, Manager+, or group leader (leaderId)
  • delete: Admin + org member

csrSettings/{settingsId}

  • read: Authenticated + org member
  • create/update: SuperAdmin or Admin in org
  • delete: SuperAdmin only

Workflow / Orchestration

approvalRoutingConfigs/{configId}, delegationSettings/{settingId}, notificationTemplateOverrides/{templateId}

  • read: Authenticated + Admin
  • create/update: SuperAdmin or Admin
  • delete: SuperAdmin only

escalationRules/{ruleId}, platformWorkflowRules/{ruleId}

  • read: Authenticated + Admin
  • create/update/delete: SuperAdmin or Admin

platformWorkflowExecutionLogs/{logId}

  • read: Authenticated + Admin
  • create: SuperAdmin or Admin
  • update/delete: SuperAdmin only

Durable Eventing

systemEvents/{eventId}, extensionEvents/{eventId}

  • read: Authenticated + org member
  • create: Authenticated + org member; userId must match caller
  • update: Authenticated + org member + event owner; cannot change organizationId, userId, or type
  • delete: Never (append-only)

eventOutbox/{eventId}

  • read: Authenticated; Admin or org member
  • create: Authenticated + org member; userId must match caller
  • update: Same as system events (owner only, immutable core fields)
  • delete: Never

eventConsumptions/{consumptionId}, eventDlq/{eventId}

  • read: Authenticated + Admin
  • write: Never (backend managed via Admin SDK)

Default Deny

Any collection not explicitly matched above is denied by default (Firestore’s implicit deny behavior). No catch-all allow rule exists.

Storage Rules (storage.rules)

Helper Functions

FunctionPurpose
isAuthenticated()request.auth != null
hasPeopleProfile()Checks if /people/{uid} exists in Firestore
getEmployee()Returns people/{uid} doc (preferred) or falls back to employees/{uid}
belongsToOrg(orgId)Authenticated + employee doc’s organizationId matches
isAdmin()systemRole is Admin or Super Admin
isSuperAdmin()systemRole is Super Admin

Storage Path Rules

PathReadWriteDelete
avatars/{userId}/{fileName}Any authenticatedOwn avatar onlyOwn avatar only
logos/{orgId}/{fileName}Org member or SuperAdminSuperAdmin or Admin in orgSuperAdmin or Admin in org
contacts/{orgId}/{fileName}Org memberOrg memberOrg member
platform/{fileName}Any authenticatedSuperAdmin onlySuperAdmin only
organizations/{orgId}/projects/{projectId}/{**}Org memberAdmin in orgAdmin in org
expenses/{orgId}/{expenseId}/{fileName}Org memberOrg memberOrg member
receipts/{orgId}/{projectId}/{expenseId}/{fileName}Org member (legacy)Org memberOrg member
project-documents/{orgId}/{projectId}/{fileName}Org memberOrg memberAdmin in org
portal-submissions/{orgId}/{projectId}/{submissionId}/{fileName}Org memberNever (signed URLs only)Admin in org
{allPaths=**} (default)NeverNeverNever

Tenant Isolation Pattern

Every rule enforces tenant isolation through one of three patterns:

Pattern 1: Direct organizationId Field

Most top-level collections store organizationId directly on each document. Rules validate:
resource.data.organizationId == getUserOrganizationId()
via the belongsToOrg() helper. This is the most common pattern.

Pattern 2: Subcollection Under Organization

Collections nested under /organizations/{orgId}/ use the path parameter:
belongsToOrg(orgId)
This covers config, widget overrides, timers, agent runs, credit reservations, module installations, and intelligence cache.

Pattern 3: User-Scoped Data with Org Check

Some collections (expenses, journals) validate both ownership and org membership:
isOwner(resource) && belongsToOrg(resource.data.organizationId)

Immutability of organizationId

All update rules that allow modifications include a check preventing organizationId changes:
request.resource.data.organizationId == resource.data.organizationId
This prevents documents from being moved between tenants.

Security Patterns

Role-Based Access Summary

OperationUserManagerAdminSuperAdmin
Read own dataYesYesYesYes
Read org dataYesYesYesYes
Create entriesOwn onlyYesYesYes
Approve/rejectNoYesYesYes
Delete recordsOwn (limited)SomeMostAll
Manage configNoNoYesYes
Cross-org accessNoNoNoYes

Immutable Audit Collections

The following collections deny all updates and deletes to maintain audit integrity:
  • auditLogs, audit_logs
  • securityEvents
  • loginAttempts
  • creditTransactions
  • badges
  • imuserationSessions
  • auditorAccessGrants
  • configurationSnapshots
  • auditExportJobs
  • systemEvents, extensionEvents, eventOutbox
  • eventConsumptions, eventDlq

Cloud Function-Only Collections

These collections deny all client writes; data is managed exclusively through Admin SDK:
  • document-chunks
  • ai-query-logs
  • query-cache
  • partnershipMetrics
  • meIndicatorSummaries
  • meWorkflowExecutionLogs
  • platform/intelligence/snapshots
  • platform/intelligence/benchmarks
  • platform/intelligence/funderProfiles
  • platform/intelligence/predictions
  • platform/intelligence/trends
  • portalSessions
  • portalReportAcknowledgments (create only)
  • portalSubmissions (create only)
  • staffAllocations
  • accrualBalances

Time-Boxed Auditor Access

Auditor access is temporary and scoped:
  1. An Admin creates an auditorAccessGrants/{uid}_{orgId} document with isActive and expiresAt
  2. isAuditor() checks that the grant is active AND expiresAt > request.time
  3. Auditors get read-only access to compliance, audit, and financial collections
  4. Grants cannot be deleted (audit trail), only deactivated

Owner-Only with Lock Pattern

Used for journals and timesheets:
  • Owners can update/delete their own entries only if isLocked == false
  • Once a manager approves (locks) an entry, the owner can no longer modify it
  • Managers can always update for approval purposes

Pre-Authentication Rules

Some security monitoring collections allow unauthenticated writes:
  • securityEvents (create: true) — needed for login failure logging
  • loginAttempts (create: true) — needed before auth is established
  • securityAlertConfigs (read: true) — needed for threshold checks during login
  • platformConfig (read: true) — needed for branding on login page
  • pendingSignups (create with field validation) — referral code storage during signup

Maintenance

Update this document whenever firestore.rules or storage.rules is modified. Key areas to check:
  • New collections require a new match block (default deny means they are inaccessible without one)
  • New roles or permissions should be reflected in the helper functions section
  • Subcollections under existing paths may need separate documentation
  • Backward-compatible collection name variants (e.g., compliance_alerts vs complianceAlerts) should be noted