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
| Status | Updated | Covered Files |
|---|
| 🟢 Stable | 2026-02-21 | features/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
| ID | Name | Category | Files | Description |
|---|
impact | Impact Visualizer | IMPACT | 64 | M&E tracking, indicators, targets, KoboToolbox integration, workflows, AI anomaly detection |
corporate-csr-hub | Corporate CSR Hub | FINANCE | 27 | Employee giving portal, matching engine, campaigns, ERG groups, impact reports |
donor-wallet | Donor Wallet | FINANCE | 24 | Personal giving wallets, multi-NGO allocations, corporate matching, recurring donations |
virtual-giving-card | Virtual Giving Card | FINANCE | 21 | Virtual prepaid cards, one-tap donations, fraud detection, NGO branding |
compliance-vault | Compliance Vault | COMPLIANCE | 17 | Document management, audit trail, checklists, deadline tracking, risk assessment |
funder-crm | Funder Relationship CRM | GRANTS | 16 | Funder profiles, pipeline tracking, interactions, stewardship, analytics |
budget-forecaster | Budget Forecaster | FINANCE | 15 | Budget scenarios, burn rate, variance, cash flow projections, alerts |
grant-calendar | Grant Calendar | GRANTS | 15 | Deadline tracking, calendar view, automated reminders |
board-portal | Board Portal | COMPLIANCE | 14 | Board member directory, meetings, resolutions, governance health |
event-fundraiser | Event Fundraiser | FINANCE | 14 | Event planning, ticketing, sponsorships, auctions, revenue tracking |
volunteer-coordinator | Volunteer Coordinator | HR | 14 | Volunteer directory, opportunities, hour logging, shift scheduling, badges |
grant-writer | Grant Writer AI | AI | 12 | AI 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:
| Field | Type | Description |
|---|
id | string | Unique identifier (e.g. grant-calendar) |
name | string | Human-readable name |
description | string | Short description |
icon | string | Lucide icon name |
category | ModuleCategory | ANALYTICS, COMPLIANCE, FINANCE, HR, GRANTS, IMPACT, INTEGRATIONS, AI, REPORTING |
version | string | Semver (e.g. 1.0.0) |
manifestVersion | 2 | Schema version |
Access Control
| Field | Type | Description |
|---|
includedInTiers | SubscriptionTier[] | Tiers where module is auto-available |
features | Feature[] | Feature enum gates |
permissions | string[] | Required RBAC permissions (e.g. VIEW_GRANT_CALENDAR, MANAGE_GRANT_CALENDAR) |
requiredModules? | string[] | Module dependency chain |
Routing
| Field | Type | Description |
|---|
basePath | string | URL prefix (e.g. /grant-calendar) |
layoutComponent | string | Layout component path |
routes | ModuleRouteEntry[] | { path, component } pairs |
sidebarEntry | ModuleSidebarEntry | { 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:
| Contribution | Registration Target | Example |
|---|
widgets | WidgetRegistryService | ”Upcoming Deadlines” card on dashboard |
commands | ActionRegistry (Command Palette) | “Open Grant Calendar” action |
eventHandlers | EventBus (scoped) | Auto-create calendar events on GRANT_WON |
settingsPanels | Organization Settings page | Module configuration panel |
agentTools | AgentToolRegistry | check_upcoming_deadlines tool |
{
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'
}
{
name: 'check_upcoming_deadlines',
description: 'Check upcoming grant deadlines within N days',
requiredPermissions: ['VIEW_GRANT_CALENDAR'],
handlerPath: './handlers/checkUpcomingDeadlines',
creditCost: 1
}
Lifecycle Hooks
| Hook | When Called |
|---|
onActivate | After all contributions are registered |
onDeactivate | Before contributions are unregistered |
onTenantSwitch | When user switches organization |
Installation System
Installation States
AVAILABLE → TRIALING → ACTIVE ⇌ SUSPENDED → INACTIVE
| State | Description |
|---|
AVAILABLE | Module exists but not installed |
TRIALING | Free trial active (with trialEndsAt) |
ACTIVE | Fully installed and operational |
SUSPENDED | Temporarily disabled (billing issue) |
INACTIVE | Uninstalled |
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
| Method | Description |
|---|
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:
| Policy | Behavior |
|---|
retain | Data stays; reinstalling restores access |
archive | Sets isArchived = true on all documents |
delete | Permanently 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
| Model | Description |
|---|
FREE | Always available at no cost |
FLAT_MONTHLY | Fixed monthly fee |
PER_SEAT | Per-user pricing |
TIER_INCLUDED | Free 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
};
| Method | Description |
|---|
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:
- Validate required module dependencies
- Register widgets →
WidgetRegistryService
- Register commands →
ActionRegistry
- Subscribe event handlers → scoped
EventBus
- Register agent tools →
AgentToolRegistry
- Call
lifecycle.onActivate hook
Deactivation Sequence: (reverse order)
- Call
lifecycle.onDeactivate hook
- Unregister agent tools
- Unsubscribe event handlers
- Unregister commands
- 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():
| Property | Type | Description |
|---|
activeModuleIds | Set<string> | Currently active module IDs |
installations | Map<string, ModuleInstallation> | Full installation records |
isModuleActive(id) | boolean | Check if a specific module is active |
activeSidebarEntries | ModuleSidebarEntry[] | Sorted sidebar items |
activeContributions | ActiveExtensionContributions | All registered contributions |
isLoading | boolean | Loading state |
refreshInstallations() | Promise<void> | Force reload from Firestore |
optimisticInstall(inst) | void | Instant UI update on install |
optimisticUninstall(id) | void | Instant UI update on uninstall |
Initialization Flow:
- App startup →
ModuleRegistry.init() (lazy-loads all manifests)
- Org change → Load installations from
organizations/{orgId}/moduleInstallations/*
- Combine manifests + installation state →
ResolvedModule[]
- Compute active module IDs (status = ACTIVE or TRIALING)
- 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
| Property | Type | Description |
|---|
resolvedModules | ResolvedModule[] | All modules with installation state |
filteredModules | ResolvedModule[] | After search/category/status filters |
filter / setFilter | MarketplaceFilter | { 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 / isUninstalling | string | null | Module 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