GrantMatch AI is a comprehensive grant discovery and management platform built for nonprofits. The application helps organizations find relevant grant opportunities from multiple sources (Federal, California, Ohio, and other funding sources), manage their grant pipeline, and collaborate with team members throughout the grant application process.
GrantMatch AI streamlines the grant discovery process by:
- Aggregating grants from multiple government and institutional sources
- Intelligent search & filtering by deadline, funding amount, geography, and grant source
- Organization-based collaboration with multi-user support and team invitations
- Detailed grant information including eligibility, funding details, and application requirements
- Organization match profiling (entity type, revenue sources, budget, staff size, preferred award range, focus/service areas, priority focus keywords) feeding the scoring engine
- Landing Page (
/) - Marketing site showcasing platform features (Smart Matching, Streamlined Applications, Impact Tracking) - Registration (
/register) - Account creation with email/password or OAuth (Google) - Onboarding (
/(auth)/onboarding) - Three-step process:- Personal Info - Name, phone, avatar upload
- Company Info - Organization details, mission/description, address, focus areas, priority focus keywords (required; up to 5, “things you’d be thrilled to get funding for”)
- Team Invites - Invite colleagues to join the organization
- Dashboard (
/dashboard) - Primary grant search interface with:- Advanced search filters (keyword, source, deadline, funding amount)
- Active filter tags with quick removal
- Paginated grant results with detailed card views
- Grant count and real-time search feedback
- Grant Details (
/grants/[id]) - Comprehensive grant information including purpose, eligibility, funding details, and application links - Profile (
/profile) - User account management plus org “Improve matches” form (entity type, revenue sources, budget/staff ranges, focus/service areas, priority focus) - Organization (
/org) - Organization settings and team management (legacy; match profile now lives on/profile)
- Frontend: Next.js 16 + React 19 with App Router
- Backend: tRPC for type-safe APIs, Prisma ORM with PostgreSQL
- Auth: NextAuth.js with credential and OAuth (Google) providers
- Styling: Tailwind CSS v4+ with Shadcn UI components
- Forms: React Hook Form with Zod validation
- State: React Query (@tanstack/react-query) for server state management
- Organization profile signals:
entityType,revenueSources,budgetRange,staffRange,minAward/maxAward(preferred award range),focusAreas,serviceAreas,priorityFocusKeywords(max 5, prioritized in purpose scoring) - Onboarding requires priority focus keywords to avoid “mushy” matching
- Grant match scoring weights purpose, eligibility, geography, funding and is versioned via
scoringVersion - New organizations and scoring-profile updates force
scoringVersionto the currentSCORING_VERSIONto ensure recompute when the model changes - Match index runner: claim/resume tick that recomputes GrantMatch rows for OPEN/UNKNOWN grants only, uses org
scoringVersion, and tracks status/cursor/claim fields on Organization - Scoring emits structured reasons (labels + optional details + strength) stored in
grantMatch.subscoresJson.reasons; the cheap “score unchanged” bump now refreshes explanation and reasons so UI always sees updated copy without requiring a full upsert
- Install dependencies:
pnpm install - Configure environment: Copy
.env.exampleto.envand set:DATABASE_URLfor PostgreSQL connection- Deployments: set
DATABASE_URLorPOSTGRES_PRISMA_URLin your hosting env (Vercel Postgres usesPOSTGRES_PRISMA_URL) soprisma generateruns during builds PRISMA_CLIENT_ENGINE_TYPEtolibrary(default in.env.example) to ensure the Node query engine is used instead of the data-proxy clientNEXTAUTH_URLandNEXT_PUBLIC_APP_URLtohttp://localhost:3005NEXTAUTH_SECRETfor session encryption- Optional:
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETfor OAuth - Optional:
RESEND_API_KEYfor email notifications
- Provision database:
./start-database.sh(regenerates local password) - Run migrations:
pnpm prisma migrate dev - Generate Prisma client:
pnpm prisma generate - Start dev server:
pnpm dev(runs on port 3005 with Turbopack)
src/app/— Next.js App Router pages and API routes(auth)/- Auth route group (register, onboarding)login/,dashboard/,grants/[id]/,profile/,org/,verify-email/- Main pagesapi/- REST endpoints (auth, grants, organizations, onboarding, grant imports) + tRPC handler
src/components/— Reusable React componentsui/- Shadcn UI primitives (button, form, card, input, select, dialog, etc.)auth/,grants/,onboarding/,profile/- Feature-specific componentslayout/- Navigation components (Header, MainNav, MobileNav, UserNav)providers/- Context providers (NextAuth, tRPC/React Query)
src/lib/— Client-side utilitiestrpc/- tRPC client configurationhooks/- Custom React hooks (useGrantSearch, etc.)types/- TypeScript type definitions- Auth helpers, formatting utilities
src/server/— Server-only coderouters/- tRPC router definitionstrpc.ts- tRPC context and procedure setup
prisma/— Database layerschema.prisma- Database schema definitionmigrations/- Database migration history- Generated Prisma client output:
src/prisma/generated/client
scripts/— Utility scripts (password generation, data seeding, etc.)
- User - User accounts with auth (email/password or OAuth), role management, organization membership
- Account - OAuth provider accounts (NextAuth.js adapter)
- Session - User sessions (NextAuth.js adapter)
- Organization - Nonprofit organizations with mission/description, address info, and matching fields (entity type, revenue sources, budget range, staff range, focus areas, service areas, priority focus keywords; match index status/cursor/claim tracking, scoringVersion)
- Invitation - Team invitations for organization collaboration (PENDING/ACCEPTED/REJECTED)
- Grant - Grant opportunities with comprehensive metadata:
- Multi-source support (FEDERAL, CALIFORNIA, OHIO, OTHER)
- Deadline tracking (CLOSED, FIXED, ONGOING, ROLLING, TBD, UNKNOWN)
- Funding details (estimated amounts, award floor/ceiling, disbursement info)
- Eligibility and geographic requirements
- Federal grant specifics (agency code, CFDA list)
- GrantDetail - Extended grant information (purpose, description, eligibility requirements, funding details as JSON)
- GrantImportRun - Batch import tracking for grant data ingestion (QUEUED, IN_PROGRESS, COMPLETED, FAILED)
- Users belong to Organizations (many-to-one)
- Organizations have many Users and Invitations
- Grants optionally have one GrantDetail (one-to-one)
- Users can create GrantImportRuns
- Dev server:
pnpm dev- Starts Next.js with Turbopack on port 3005 - Build:
pnpm build- Runsprisma generatethennext build - Lint:
pnpm lint- Runs Next.js ESLint config with TypeScript checking - Start production:
pnpm start- Starts production server - Database setup:
./start-database.sh- Provisions local PostgreSQL - Migrations:
pnpm prisma migrate dev- Apply schema changes to database - Generate client:
pnpm prisma generate- Regenerate Prisma client after schema changes - Import California grants:
pnpm import:california- Parsesource_files/california-grants-portal-data.csvvia the unified ingestion core - Sync CA grants (CKAN):
pnpm sync:ca-grants- Runs the CKAN-based nightly sync locally (uses unified ingestion core) - Sync Federal grants (index):
pnpm sync:federal-grants- Runs the Grants.gov index ingest (no detail fetch; details are on-demand) - Prisma Studio:
pnpm prisma studio- Visual database browser
- Vercel Cron hits
/api/cron/sync-federalevery 6 hours and/api/cron/sync-californianightly (seevercel.json). - Protect with
CRON_SECRETin Vercel Project Settings (or.envlocally). Requests must sendAuthorization: Bearer <CRON_SECRET>. - Endpoints are lock-aware: if a recent run is in progress, they return
{ ok: true, skipped: true, reason: "lock-held" }instead of starting a duplicate. - Local test example:
curl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3005/api/cron/sync-federal.
- Sources: Supports multiple sources (California CKAN and CSV, Federal index live; future-ready for others).
- Identity: Each grant carries
source,sourceRecordId(preferred upstream id),sourceKey(hash fallback), andnumber(unique per-source). Portal IDs are deprecated for uniqueness. - Change detection:
contentHashdetects field changes;status(OPEN/CLOSED/UNKNOWN) andclosedAttrack deadline/explicit closures;lastSeenAtmarks sync freshness. - Run logging:
GrantSyncRunrecords each ingest with counters and schema snapshot;GrantChangelogs CREATED/UPDATED/CLOSED/REOPENED/STATUS_CHANGED (plusDETAILS_FETCHEDfor on-demand detail pulls) with hashes and diffs.
- Ensure
.envhasDATABASE_URL; setTZ=America/Los_Angelesin your environment if scheduling. - Run locally:
pnpm sync:ca-grants. - Output:
GrantSyncRun+GrantChangerows capture counts/errors;Grantrows are upserted idempotently.
pnpm import:californiauses the same unified ingestion core as CKAN, so identity/contentHash/status handling and logging are consistent.
- Nightly index ingest (no full details):
pnpm sync:federal-grants(envs:FEDERAL_GRANTS_ELIGIBILITIESdefault12|13;FEDERAL_GRANTS_ROWSdefault500;FEDERAL_GRANTS_OPP_STATUSESdefaultposted|forecasted|closed|archived). - Grant details are fetched on-demand when viewing a grant page or hitting
/api/grants/[id]/details; failures don’t block page rendering. On-demand detail fetch logs aGrantChangewith changeTypeDETAILS_FETCHED.
- Apply schema changes:
pnpm prisma migrate dev(orpnpm prisma migrate resetto wipe dev data). - Regenerate client after schema updates:
pnpm prisma generate.
pnpm lintenforces the Next.js ESLint config with Prettier + Tailwind; keep output clean before committingpnpm typecheckruns the TypeScript compiler without emitting; use it to catch API/server regressions early- Add Vitest or Playwright coverage where relevant; colocate specs as
*.test.tsor*.spec.tsx - Cover critical tRPC procedures and Prisma flows, and note manual QA in PR descriptions
- Use Server Components by default; add "use client" only when needed for interactivity
- Prefer tRPC calls over direct Prisma access in UI components
src/app/api/gp/start/route.tsandsrc/app/api/gp/import/route.tsare unauthenticated and accept arbitrary payloads; protect them with NextAuth, a shared secret header, or signed webhooks before exposing externally.src/app/api/user/route.tsis a stubbed PATCH handler that always succeeds without writing to the database; wire it to the User model before relying on profile edits.src/app/api/grants/[id]/details/route.tsswallows detail-fetch errors and always returns a response; consider surfacing failures (504/502) or returning the last-known payload with an error flag so the UI can react.- Automated tests currently cover only grant detail mapping; add coverage for API routes, ingestion flows, and service-layer logic to prevent regressions.
- Single Responsibility: Keep feature logic inside its route folder and move shared UI into
src/components/ui. - Open/Closed: Extend via new components or tRPC procedures instead of modifying stable code.
- Liskov Substitution: Define clear prop types and router contracts so alternatives drop in safely.
- Interface Segregation: Expose focused hooks/services rather than broad utility objects.
- Dependency Inversion: Have UI layers consume abstractions (tRPC calls, auth helpers) instead of direct Prisma access.
- NextAuth.js v4 with custom credential provider
- Support for OAuth (Google) and email/password authentication
- Organization-based access control
- Email verification flow
- Session management with database persistence
- Multi-source grant aggregation (Federal via grants.gov, state-level sources)
- Advanced search with compound filters (keyword, source, deadline range, funding amount)
- Paginated results with configurable page sizes
- Per-user bookmarking with statuses (Interested/Applied/Not for us), notes, and tags (max 10 per bookmark, 100 bookmarks/user) plus quick tag from dashboard cards and profile list filters
- Detailed grant views with rich metadata
- Import system for batch grant data ingestion with status tracking
- Multi-tenant architecture with organization isolation
- Team invitation system with email-based workflow
- Role-based access (USER, ADMIN)
- Shared grant pipeline across organization members
- Responsive design with mobile-first approach
- Dark mode support throughout
- Loading states with React Suspense and skeleton components
- Error boundaries for graceful error handling
- Toast notifications for user feedback (Sonner)
- Animated gradients and visual polish on marketing pages
- Custom Prisma client output path (
src/prisma/generated/client) - JSON columns for flexible metadata (eligibility requirements, CFDA lists, funding details)
- Composite unique constraints for data integrity (portalId + source, email + organizationId)
- Optimistic date handling for various deadline types
- Use short, present-tense commits (
added search filters on dashboard) and group related work; reference issues with#idwhen available - PRs need a concise summary, visuals for UI work, and explicit notes on schema or env changes
- Confirm
pnpm lintpasses and Prisma commands (migrate dev,generate) ran successfully - See
AGENTS.mdfor deeper contributor guidance and AI agent workflows - See
CLAUDE.mdfor Claude Code-specific development guidelines