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.

Extension Marketplace

StatusUpdatedCovered Files
🟢 Stable2026-02-21features/extensions/contracts.ts, shared/platform/ExtensionRegistry.ts, shared/platform/ExtensionInstallationService.ts, shared/platform/ExtensionActivationService.ts, contexts/ExtensionContext.tsx, features/extensions/, extensions/*/manifest.ts

Overview

The Extension Marketplace is a first-party, in-monorepo module system. Extensions are self-contained feature bundles that organizations can install to add capabilities beyond the core platform.

Three-Layer Architecture

┌─────────────────────────────────────────────────────────┐
│  Manifest Layer (compile-time)                          │
│  Static TypeScript declarations in extensions/*/manifest│
├─────────────────────────────────────────────────────────┤
│  Registry Layer (app startup)                           │
│  ModuleRegistry lazy-loads manifests, resolves state    │
├─────────────────────────────────────────────────────────┤
│  Runtime Layer (per-organization)                       │
│  ModuleContext tracks active modules, contributions     │
└─────────────────────────────────────────────────────────┘

Extension Catalog

IDNameCategoryFilesDescription
impactImpact VisualizerIMPACT64M&E tracking, indicators, targets, KoboToolbox integration, workflows, AI anomaly detection
corporate-csr-hubCorporate CSR HubFINANCE27Employee giving portal, matching engine, campaigns, ERG groups, impact reports
donor-walletDonor WalletFINANCE24Personal giving wallets, multi-NGO allocations, corporate matching, recurring donations
virtual-giving-cardVirtual Giving CardFINANCE21Virtual prepaid cards, one-tap donations, fraud detection, NGO branding
compliance-vaultCompliance VaultCOMPLIANCE17Document management, audit trail, checklists, deadline tracking, risk assessment
funder-crmFunder Relationship CRMGRANTS16Funder profiles, pipeline tracking, interactions, stewardship, analytics
budget-forecasterBudget ForecasterFINANCE15Budget scenarios, burn rate, variance, cash flow projections, alerts
grant-calendarGrant CalendarGRANTS15Deadline tracking, calendar view, automated reminders
board-portalBoard PortalCOMPLIANCE14Board member directory, meetings, resolutions, governance health
event-fundraiserEvent FundraiserFINANCE14Event planning, ticketing, sponsorships, auctions, revenue tracking
volunteer-coordinatorVolunteer CoordinatorHR14Volunteer directory, opportunities, hour logging, shift scheduling, badges
grant-writerGrant Writer AIAI12AI proposal drafting, templates, boilerplate library, peer review, submission tracking

Dependencies

  • corporate-csr-hub requires donor-wallet
  • virtual-giving-card optionally integrates with donor-wallet

Extension File Structure

Each extension under src/extensions/{id}/ follows this standard layout:
{id}/
├── manifest.ts                 # ModuleManifest declaration
├── index.ts                    # Public API (exports manifest)
├── types.ts                    # Extension-specific types
├── context/
│   └── {Id}Context.tsx         # Extension state provider
├── components/
│   ├── {Id}Layout.tsx          # Top-level layout/router
│   ├── pages/                  # Page components
│   └── widgets/                # Dashboard widgets
├── services/                   # Business logic
├── hooks/
│   ├── onActivate.ts           # Lifecycle: called after activation
│   ├── onDeactivate.ts         # Lifecycle: called before deactivation
│   └── onTenantSwitch.ts       # Lifecycle: org switch cleanup
└── handlers/
    └── on{Event}.ts            # EventBus handlers

ModuleManifest (v2)

File: src/features/extensions/contracts.ts Each extension exports a manifest: ModuleManifest with these sections:

Metadata

FieldTypeDescription
idstringUnique identifier (e.g. grant-calendar)
namestringHuman-readable name
descriptionstringShort description
iconstringLucide icon name
categoryModuleCategoryANALYTICS, COMPLIANCE, FINANCE, HR, GRANTS, IMPACT, INTEGRATIONS, AI, REPORTING
versionstringSemver (e.g. 1.0.0)
manifestVersion2Schema version

Access Control

FieldTypeDescription
includedInTiersSubscriptionTier[]Tiers where module is auto-available
featuresFeature[]Feature enum gates
permissionsstring[]Required RBAC permissions (e.g. VIEW_GRANT_CALENDAR, MANAGE_GRANT_CALENDAR)
requiredModules?string[]Module dependency chain

Routing

FieldTypeDescription
basePathstringURL prefix (e.g. /grant-calendar)
layoutComponentstringLayout component path
routesModuleRouteEntry[]{ path, component } pairs
sidebarEntryModuleSidebarEntry{ label, icon, to, permission, position, sortOrder }

Data Collections

dataCollections: ModuleDataCollection[] = [
  {
    name: string;              // Firestore collection name
    label: string;             // Human-readable label
    uninstallPolicy: 'retain' | 'archive' | 'delete';
    orgIdField?: string;       // Tenant scoping field (default: 'organizationId')
  }
]

Contributions

Extensions register runtime contributions via the contributions object:
ContributionRegistration TargetExample
widgetsWidgetRegistryService”Upcoming Deadlines” card on dashboard
commandsActionRegistry (Command Palette)“Open Grant Calendar” action
eventHandlersEventBus (scoped)Auto-create calendar events on GRANT_WON
settingsPanelsOrganization Settings pageModule configuration panel
agentToolsAgentToolRegistrycheck_upcoming_deadlines tool

Widget Contribution

{
  id: 'upcoming-deadlines',
  title: 'Upcoming Deadlines',
  description: 'Shows the next 5 grant deadlines',
  category: 'grants',
  componentPath: './components/widgets/UpcomingDeadlinesWidget',
  icon: 'CalendarClock',
  size: 'standard' | 'compact' | 'chart' | 'large',
  requiredPermissions?: ['VIEW_GRANT_CALENDAR']
}

Command Contribution

{
  id: 'open-calendar',
  label: 'Open Grant Calendar',
  group: 'navigation' | 'create' | 'context' | 'ai',
  action: '/grant-calendar/calendar',
  keywords: ['calendar', 'deadlines'],
  permission: 'VIEW_GRANT_CALENDAR',
  priority: 10,
  section: 'Grant Calendar'
}

Agent Tool Contribution

{
  name: 'check_upcoming_deadlines',
  description: 'Check upcoming grant deadlines within N days',
  requiredPermissions: ['VIEW_GRANT_CALENDAR'],
  handlerPath: './handlers/checkUpcomingDeadlines',
  creditCost: 1
}

Lifecycle Hooks

HookWhen Called
onActivateAfter all contributions are registered
onDeactivateBefore contributions are unregistered
onTenantSwitchWhen user switches organization

Installation System

Installation States

AVAILABLE → TRIALING → ACTIVE ⇌ SUSPENDED → INACTIVE
StateDescription
AVAILABLEModule exists but not installed
TRIALINGFree trial active (with trialEndsAt)
ACTIVEFully installed and operational
SUSPENDEDTemporarily disabled (billing issue)
INACTIVEUninstalled

Firestore Schema

organizations/{orgId}/moduleInstallations/{moduleId}
{
  id: string,                        // "{orgId}_{moduleId}"
  organizationId: string,
  moduleId: string,
  status: ModuleInstallStatus,
  installedVersion: string,
  installedAt: ISO 8601,
  installedBy: string,               // User ID
  trialEndsAt?: ISO 8601,
  stripeSubscriptionItemId?: string,
  settings: Record<string, unknown>,  // Module-specific config
  statusHistory: StatusChange[]       // Audit trail
}

ModuleInstallationService

File: src/shared/platform/ExtensionInstallationService.ts
MethodDescription
install(orgId, moduleId, userId, options?)Create installation record, activate module
uninstall(orgId, moduleId, userId, options?)Deactivate and cleanup per uninstallPolicy
getAllInstallations(orgId)Load all installations for an organization
getModuleDataStats(orgId, moduleId)Count documents in module’s data collections
cleanupModuleData(orgId, moduleId)Execute cleanup per collection’s uninstall policy
Data Cleanup Policies:
PolicyBehavior
retainData stays; reinstalling restores access
archiveSets isArchived = true on all documents
deletePermanently removes documents (batched in groups of 500)

Billing-Aware Installation Flow

User clicks "Install" in Marketplace

        ├── Tier-included? → Direct activation (no Stripe)

        ├── Trial requested? → startModuleTrial() via Cloud Function
        │                      → Create installation with trialEndsAt

        └── Paid module? → addModuleToSubscription()
                           → Stripe Checkout session
                           → Webhook updates installation on payment

Pricing Models

ModelDescription
FREEAlways available at no cost
FLAT_MONTHLYFixed monthly fee
PER_SEATPer-user pricing
TIER_INCLUDEDFree with certain subscription tiers

Registry System

ModuleRegistry

File: src/shared/platform/ExtensionRegistry.ts The compile-time registry uses lazy imports to avoid circular dependencies:
const MANIFEST_LOADERS = {
  'impact':               () => import('@/extensions/impact/manifest'),
  'grant-calendar':       () => import('@/extensions/grant-calendar/manifest'),
  'donor-wallet':         () => import('@/extensions/donor-wallet/manifest'),
  // ... all 12 extensions
};
MethodDescription
init()Load all manifests (called at app startup)
getManifest(moduleId)Sync lookup by ID
getAllManifests()Get all registered manifests
resolveModules(installations)Combine manifests with Firestore installation state
getLazyComponent(moduleId, path)React.lazy() with internal cache
getActiveSidebarEntries(activeIds)Sorted sidebar items for active modules

ExtensionActivationService

File: src/shared/platform/ExtensionActivationService.ts Orchestrates contribution registration on module activation: Activation Sequence:
  1. Validate required module dependencies
  2. Register widgets → WidgetRegistryService
  3. Register commands → ActionRegistry
  4. Subscribe event handlers → scoped EventBus
  5. Register agent tools → AgentToolRegistry
  6. Call lifecycle.onActivate hook
Deactivation Sequence: (reverse order)
  1. Call lifecycle.onDeactivate hook
  2. Unregister agent tools
  3. Unsubscribe event handlers
  4. Unregister commands
  5. Unregister widgets

Active Contributions Tracking

interface ActiveExtensionContributions {
  widgets: Map<string, ExtensionWidgetContribution[]>;
  commands: Map<string, ExtensionCommandContribution[]>;
  agentTools: Map<string, ExtensionAgentToolContribution[]>;
  settingsPanels: Map<string, SettingsPanelEntry[]>;
}

Runtime Context

ModuleContext

File: src/contexts/ExtensionContext.tsx Provides module state to all components via useModules():
PropertyTypeDescription
activeModuleIdsSet<string>Currently active module IDs
installationsMap<string, ModuleInstallation>Full installation records
isModuleActive(id)booleanCheck if a specific module is active
activeSidebarEntriesModuleSidebarEntry[]Sorted sidebar items
activeContributionsActiveExtensionContributionsAll registered contributions
isLoadingbooleanLoading state
refreshInstallations()Promise<void>Force reload from Firestore
optimisticInstall(inst)voidInstant UI update on install
optimisticUninstall(id)voidInstant UI update on uninstall
Initialization Flow:
  1. App startup → ModuleRegistry.init() (lazy-loads all manifests)
  2. Org change → Load installations from organizations/{orgId}/moduleInstallations/*
  3. Combine manifests + installation state → ResolvedModule[]
  4. Compute active module IDs (status = ACTIVE or TRIALING)
  5. Activate each active module → register contributions

Marketplace UI

ExtensionsPage

File: src/features/extensions/components/ExtensionsPage.tsx The marketplace storefront with:
  • Hero section with category showcase
  • Floating search bar with fuzzy matching
  • Category-driven filtering with visual themes
  • Extension cards (icon, name, description, install button, pricing badge, tier badge)
  • Detail drawer with extended metadata
  • Installation confirmation modal
  • Uninstall confirmation with data cleanup options
  • Billing preview for paid modules

useMarketplace Hook

File: src/features/extensions/hooks/useExtensions.ts
PropertyTypeDescription
resolvedModulesResolvedModule[]All modules with installation state
filteredModulesResolvedModule[]After search/category/status filters
filter / setFilterMarketplaceFilter{ search, category, showInstalled }
installModule(id, opts?)Promise<ModuleCheckoutResult>Billing-aware install
uninstallModule(id, purge?)Promise<void>Uninstall with optional data purge
previewBilling(id)Promise<ModuleBillingPreview>Show cost before install
isInstalling / isUninstallingstring | nullModule ID in progress

Permissions

Each extension defines its own permission set, typically:
  • VIEW_{EXTENSION} — Read access
  • MANAGE_{EXTENSION} — Create/update access
  • ADMIN_{EXTENSION} — Full control including settings
The Impact module has 11 granular permissions (e.g. VIEW_ME, MANAGE_ME_LIBRARY, APPROVE_ME_DATA). All permissions integrate with the platform RBAC system.

Maintenance

Update this document when:
  • Adding a new extension to the marketplace
  • Changing the manifest schema version
  • Modifying the installation/activation flow
  • Adding new contribution types
  • Changing billing integration