SfSkills Domains Docs GitHub

Home / data / nonprofit-data-architecture

nonprofit-data-architecture

Use this skill when designing or querying the NPSP data model — constituent 360, household accounts, giving history rollups, and program participation. Trigger keywords: NPSP data model, household account, constituent record, giving rollups, CRLP, program engagement, ServiceDelivery, npo02__ fields. NOT for NPSP namespace prefixes, GAU allocations or recurring-donation object API names — use data/npsp-data-model. NOT for the Nonprofit Cloud (NPC) data model or an NPSP-to-NPC move — use architect/nonprofit-cloud-vs-npsp-migration.

Open the skill folder on GitHub


Nonprofit Data Architecture

Use this skill when working with the NPSP (Nonprofit Success Pack) data model, including constituent 360 design, household account configuration, giving history rollup fields, and program participation tracking via the Program Management Module (PMM). This skill does NOT cover the Nonprofit Cloud (NPC) data model, FSC household groups, or generic Salesforce data modeling patterns.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

The Three-Layer Constituent 360 Model

NPSP models a constituent as three interconnected layers:

  1. HH_Account (Household Account) — A standard Account record with the HH_Account record type (API name HH_Account). The household groups one or more related Contacts (family members, partners). The account is the financial unit: Opportunity and npo02__OppPayment__c records link to the Account, not to individual Contacts. Rollup summary fields on the Household Account aggregate giving totals across all members.
  2. Contact — The individual constituent. NPSP adds the npe01__Primary_Address_Type__c, npe01__Home_Address__c, and related address management fields. A Contact's AccountId points to their Household Account. The npe01__Primary_Contact__c field on the Account identifies the primary contact for the household.
  3. npo02__ rollup fields on Contact and Account — NPSP drives a constellation of rollup fields under the npo02__ namespace. Key fields include:
    • npo02__TotalOppAmount__c — lifetime total giving
    • npo02__LastOppAmount__c — most recent gift amount
    • npo02__LastCloseDate__c — most recent gift close date
    • npo02__OppAmountLastYear__c — giving in the prior calendar year
    • npo02__ConsecutiveYearsGiven__c — streak counter

    These fields exist on both Contact and Account. The values are recalculated by either the CRLP engine or the legacy Household Rollup batch job, not by standard rollup summary fields.

Customizable Rollups (CRLP) Engine

CRLP is the modern NPSP rollup calculation engine introduced in NPSP 3.x. It replaces the nightly RLLP_OppRollup_BATCH with a near-real-time engine driven by Rollup__mdt custom metadata type records. Each Rollup__mdt record defines:

Adding custom rollup fields requires creating new Rollup__mdt and Filter_Group__mdt records through the CRLP UI (Setup > Nonprofit Success Pack > Customizable Rollups). Direct API manipulation of these metadata records without understanding the required lookup relationships will break rollup calculations silently.

Program Management Module (PMM) Object Model

PMM is a separate managed package (pmdm__ namespace) that tracks program enrollment and service delivery. Its core objects are:

PMM objects are related to NPSP Contacts but PMM does not share namespace or package dependencies with NPSP core. An org can run PMM without NPSP, though this is rare.


Common Patterns

Constituent 360 Query: Full Giving and Program History for a Contact

When to use: Building a constituent summary view, reporting dashboard, or data migration validation that requires giving history, rollup totals, and program participation.

How it works:

-- Get constituent giving summary (rollup fields live on both Contact and HH Account)
SELECT
    Id,
    Name,
    AccountId,
    Account.Name,
    npo02__TotalOppAmount__c,
    npo02__LastOppAmount__c,
    npo02__LastCloseDate__c,
    npo02__ConsecutiveYearsGiven__c,
    npo02__OppAmountLastYear__c
FROM Contact
WHERE Id = :contactId

-- Get individual gift transactions for this constituent's household
SELECT Id, Name, Amount, CloseDate, StageName, npsp__Primary_Contact__c
FROM Opportunity
WHERE AccountId = :householdAccountId
  AND IsWon = true
ORDER BY CloseDate DESC

-- Get program participation
SELECT
    Id,
    pmdm__Program__r.Name,
    pmdm__Stage__c,
    pmdm__StartDate__c
FROM pmdm__ProgramEngagement__c
WHERE pmdm__Contact__c = :contactId

-- Get service deliveries
SELECT
    Id,
    pmdm__DeliveryDate__c,
    pmdm__Quantity__c,
    pmdm__Service__r.Name,
    pmdm__ProgramEngagement__r.pmdm__Program__r.Name
FROM pmdm__ServiceDelivery__c
WHERE pmdm__Contact__c = :contactId
ORDER BY pmdm__DeliveryDate__c DESC

Why not the alternative: Querying only the Contact record misses the gift transactions on the Household Account. Rollup fields may lag real-time transactions by the CRLP recalculation window; always query raw Opportunities for authoritative transaction data.

Custom Rollup via CRLP Configuration

When to use: An org needs a rollup value not covered by NPSP's default rollup fields (e.g., total giving to a specific campaign, major gift count above a threshold).

How it works:

  1. In Setup, navigate to Nonprofit Success Pack > Customizable Rollups.
  2. Create a new Filter_Group__mdt record defining the criteria (e.g., Type = 'Major Gift').
  3. Create a new Rollup__mdt record pointing to the filter group, source field (Amount), operation (SUM), target object (Contact), and target field (a custom currency field you pre-create).
  4. Run the Recalculate All Rollups batch job to populate historical data.
  5. After enabling, CRLP will update the field in near-real-time on new and changed Opportunities.

Why not the alternative: Standard Salesforce rollup summary fields on Account do not understand the Contact-to-Household Account relationship for per-Contact aggregation. Formula fields cannot aggregate across multiple records. Custom Apex triggers on Opportunity are fragile and conflict with NPSP's own Opportunity trigger framework.


Decision Guidance

SituationRecommended ApproachReason
Need lifetime giving total for a ContactRead npo02__TotalOppAmount__c on ContactMaintained by CRLP; faster than aggregate SOQL for display purposes
Need authoritative real-time totalAggregate SOQL on Opportunity WHERE AccountId = HH AccountRollup fields may lag by CRLP recalculation window
Need program enrollment historyQuery pmdm__ProgramEngagement__c WHERE pmdm__Contact__cPMM junction object; requires pmdm__ package installed
Need to add a custom donation rollupCreate Rollup__mdt + Filter_Group__mdt via CRLP UISafest extension path; avoids conflicts with NPSP trigger framework
Org is on Nonprofit Cloud (NPC), not NPSPStop — use NPC data model skill insteadNPC and NPSP use incompatible foundational objects; NPSP patterns do not apply
Org has FSC Household Groups alongside NPSPConfirm packages; treat as separate architecturesFSC uses AccountContactRelationship-based Household Group object, not HH_Account record type

Step-by-step instructions for an AI agent or practitioner working on this task:

  1. Verify the installed package set. Confirm npo02__ namespace is queryable (NPSP core), whether pmdm__ is installed (PMM), and whether npsp__ settings records exist. Run SELECT Id, Name FROM npo02__Household_Settings__c LIMIT 1 — if it returns data, NPSP is installed and active.
  2. Identify the constituent model. Query SELECT Id, Name FROM RecordType WHERE SObjectType = 'Account' AND Name = 'Household Account' to confirm the HH_Account record type is present. If absent, the org may be on One-to-One or Individual model — do not assume HH_Account model.
  3. Check CRLP status. Navigate to NPSP Settings > Rollup Settings to confirm CRLP is enabled or disabled. In CRLP-enabled orgs, do not modify npo02__Settings__c.npo02__Rollup_N_Day_Value__c or related legacy fields — they are ignored by the CRLP engine and changes create confusion.
  4. Design queries using the three-layer model. For giving data, join Contact → Account (HH_Account) → Opportunity. For program data, join Contact → pmdm__ProgramEngagement__c → pmdm__Program__c and pmdm__ServiceDelivery__c. Never query Opportunity.ContactId for NPSP gift records — NPSP stores the primary contact via npsp__Primary_Contact__c on Opportunity, not via a standard contact role in all configurations.
  5. Validate rollup field values against raw transactions. Before trusting any npo02__ rollup field in a report or migration, verify it matches an aggregate SOQL query on the underlying Opportunities. Stale rollups from legacy batch mode or a failed CRLP job are a common source of data quality defects.
  6. Document namespace dependencies in any solution. Any SOQL, Flow, or Apex that references npo02__, npsp__, npe01__, or pmdm__ fields will fail in orgs without those packages. Add an org-detection guard at the start of any script that accesses these fields.
  7. Run the NPSP Health Check before any schema change. Available under Nonprofit Success Pack > System Tools > NPSP Health Check. Identifies configuration problems that would cause your changes to behave unexpectedly.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Opportunities link to the Household Account, not to the Contact — In the NPSP Household Account model, Opportunity.AccountId points to the HH_Account, not to the individual Contact. The relationship to the primary constituent is stored in npsp__Primary_Contact__c (a lookup to Contact on the Opportunity object). Code or reports that filter Opportunity.ContactId IS NOT NULL to find constituent gifts will return no results or incorrect results.
  2. CRLP and legacy rollup mode cannot safely coexist — Orgs that partially migrate to CRLP without disabling the legacy RLLP_OppRollup_BATCH scheduled job will run both engines simultaneously. The result is unpredictable: rollup fields may be doubled, zeroed, or overwritten in alternating directions. Always confirm one engine is fully disabled before enabling the other.
  3. PMM is a separate managed package from NPSP core — The pmdm__ namespace is the Program Management Module package, installed separately from NPSP. It is common to encounter NPSP orgs where PMM is not installed. Any code, Flow, or SOQL referencing pmdm__ServiceDelivery__c, pmdm__ProgramEngagement__c, or pmdm__Program__c will throw errors in NPSP orgs without PMM. Always check installed packages before assuming PMM objects are available.
  4. HH Account record type name is configurable — The default record type for household accounts is named "Household Account" but NPSP allows it to be renamed. Queries using RecordType.Name = 'Household Account' will break in orgs where the record type was renamed. Use RecordType.DeveloperName = 'HH_Account' (the developer name is fixed) for reliable filtering.
  5. npo02__ rollup fields exist on Contact AND on Account — with different scopes — Rollup fields on the Contact aggregate gifts where npsp__Primary_Contact__c equals that Contact. Rollup fields on the Household Account aggregate all gifts linked to that Account, regardless of primary contact. Both sets have the same field names (e.g., npo02__TotalOppAmount__c). Using the wrong object in a report produces systematically wrong totals without any error.

Output Artifacts

ArtifactDescription
Constituent 360 data model diagramThree-layer NPSP model: HH_Account → Contact → npo02__ rollup fields, with Opportunity and PMM objects mapped
SOQL query libraryNamespace-aware queries for giving history, rollup validation, and program participation
CRLP configuration checklistSteps to add or validate a custom rollup via Rollup__mdt and Filter_Group__mdt
PMM availability guardPython/Apex pattern to detect PMM installation before accessing pmdm__ objects

Package files