> ## Documentation Index
> Fetch the complete documentation index at: https://docs.virtuous.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Modeling Best Practices

> How to model your platform's data inside Virtuous so it stays clean, reportable, and reconcilable over time — Contact types, designation structures, custom field design, and external reference conventions.

Most partner integrations look correct on day one and degrade slowly over months as data quality issues accumulate. The integrations that stay clean for years share a small set of data-modeling decisions made deliberately at the start. This page consolidates those decisions into a single reference — drawn from the Concepts and Workflows pages but distilled into actionable guidance.

The audience is the engineering team designing a new integration, or the team responsible for an existing integration that's starting to develop data quality issues. None of this is binding — every customer has their own preferences — but each recommendation reflects what tends to work across many partner integrations.

## Choose Contact types deliberately

The `contactType` field — `Household`, `Organization`, or `Foundation` — determines how Virtuous treats the record throughout the platform. Get this right at creation time; changing it later is operationally awkward.

| Contact type   | Use for                                                                                                                                        |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `Household`    | Individuals and family units. The default for most donor records. Includes one or more ContactIndividuals (spouses, partners, family members). |
| `Organization` | For-profit companies, businesses, corporate sponsors, vendor partners. Has organizational data (Tax ID, industry) rather than individual data. |
| `Foundation`   | Grant-making foundations and family foundations. A specialized Organization with grant-tracking conventions.                                   |

The wrong choice causes downstream pain:

* Creating an Organization as a Household means the organization shows up alongside individuals in donor reports, distorting per-household metrics.
* Creating a Household as an Organization means the individual lacks ContactIndividuals to capture their name, birth date, or personal preferences.
* Creating a Foundation as a generic Organization misses the foundation-specific reporting Virtuous provides.

### How to detect Contact type from your source data

Your source platform usually has enough signal to distinguish:

| Signal                                                                                  | Likely Contact type |
| --------------------------------------------------------------------------------------- | ------------------- |
| Source record has a `business_name` field populated, no individual first/last name      | Organization        |
| Source records carry IRS Form 990 / Schedule I data, or have grant-tracking conventions | Foundation          |
| Default for everything else                                                             | Household           |

For ambiguous cases (a sole proprietor giving from a business name with their personal email), default to Household and tag the record `"Business-Donor"` for the customer's team to review and reclassify if needed.

***

## Use a stable, predictable external reference

The `referenceSource` + `referenceId` pair is the single most important data-modeling decision for a partner integration. Every other matching, deduplication, and reconciliation pattern depends on it.

Three rules for the reference:

| Rule                                                           | Why                                                                                                                                                                                          |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`referenceSource` is a fixed string per integration.**       | Use the same value (e.g., `"Stripe"`, `"Mailchimp"`, your platform's name) for every record your integration writes. Never vary by customer or environment.                                  |
| **`referenceId` is the source platform's stable primary key.** | Use the Stripe Customer ID, the Mailchimp Subscriber Hash, your platform's user ID. Not the email, not a generated UUID, not the order ID — the persistent identifier for the entity itself. |
| **The pair is unique across all your integration's writes.**   | One donor on your platform should have exactly one (`source`, `id`) pair in Virtuous, even if they exist in multiple places on your platform.                                                |

### What "stable" means in practice

A reference is stable if it doesn't change when the entity's other attributes change. Specifically:

* **Email is not stable** — donors change their email.
* **Phone is not stable** — donors change their phone.
* **Name is not stable** — donors change their name (marriage, transition).
* **Address is not stable** — donors move.
* **Stripe Customer ID is stable** — once issued, it identifies the same customer for their lifetime on Stripe.
* **Your platform's user ID is stable** — assuming you use a real primary key, not a hash of mutable data.

The reference must survive every other field changing on the record.

<Warning>
  A common partner integration mistake: using the donation's order ID as the Contact's `referenceId`. The order ID is unique per order, so the same donor giving twice produces two Contacts. Use the donor's stable identifier (Customer ID, User ID) for Contact references; use the order or charge ID for Gift `transactionId`.
</Warning>

### When the source doesn't have a stable ID

Some source platforms (older systems, CSV imports, manual data entry) don't expose a stable identifier. Two patterns:

* **Generate one once and persist it.** On first encounter, generate a UUID and store it alongside the source record. Use the UUID as `referenceId` on every subsequent submission. This works for any source with a writable database on your side.
* **Use a deterministic hash.** Hash the most stable subset of fields (e.g., `lowercase(email) + lowercase(lastname)`) to produce a reproducible ID. Less robust because the inputs can still change, but workable for read-only sources where you have no writable storage.

***

## Project codes are the API's primary key for designations

Projects are the destination for Gift designations. Their `projectCode` field is the partner-friendly API identifier — short, human-readable, stable.

Three best practices:

| Practice                                                                                                   | Why                                                                                                                                                  |
| ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Use upper-case alphanumeric codes with hyphens.**                                                        | `CLEAN-WATER`, `GEN-FUND`, `ANNUAL-2024`. Easy to type, easy to read in logs, doesn't collide with anything else.                                    |
| **Avoid embedding the year unless the Project is genuinely year-specific.**                                | `GEN-FUND` is reusable; `GEN-FUND-2024` becomes stale next year. Year-specific codes belong on Campaign-tied Projects (e.g., `MARATHON-2024`).       |
| **Document the mapping from your platform's destinations to Virtuous Project codes at integration setup.** | Cache the mapping in your integration's configuration. Validate against `GET /api/Project/Query` at startup and after any customer-initiated change. |

### Splitting designations: when to use multiple

A single Gift can be split across multiple Projects via the `giftDesignations[]` array. Use a split when:

* The donor explicitly specifies multiple destinations.
* The source platform records a multi-Project intent (e.g., "10% to admin, 90% to programs").
* The customer's policy automatically allocates portions to specific funds (e.g., "5% of every gift to an endowment").

Don't use a split as a workaround for missing designations. If you don't know which Project the gift belongs to, don't split it across multiple defaults — flag it for the customer's team to resolve.

***

## Custom fields vs. tags: a decision tree

The two main ways to add structure beyond Virtuous's built-in fields. They serve different purposes.

| Use a tag when                                                         | Use a custom field when                                                    |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| The value is essentially boolean: "is this donor in segment X or not?" | The value is structured data: a date, number, dropdown selection, or text. |
| Many records share the same value, and you want to filter by it.       | The value varies per record and represents a specific attribute.           |
| The category is owned by the customer's marketing/segmentation team.   | The category is owned by the customer's operations or finance team.        |
| The list of possible values is short and stable.                       | The list of possible values changes frequently or is unbounded.            |

### Examples

| Concept                                           | Tag or custom field | Why                                                   |
| ------------------------------------------------- | ------------------- | ----------------------------------------------------- |
| "Major Donor" classification                      | Tag                 | Boolean segmentation, owned by the team that uses it. |
| Donor's preferred greeting                        | Custom field        | Per-record data, structured, owned by communications. |
| Subscription source (Mailchimp, Constant Contact) | Tag with prefix     | Boolean per source; useful for segmentation.          |
| Last platform sync timestamp                      | Custom field        | Time-series data, per-record, used for diagnostics.   |
| Subscriber engagement tier                        | Custom field        | Multi-valued (Bronze/Silver/Gold), structured.        |

### Tag naming conventions

When your integration writes tags, use a consistent prefix that identifies the source:

| Prefix    | Meaning                                |
| --------- | -------------------------------------- |
| `MC:`     | From Mailchimp                         |
| `CC:`     | From Constant Contact                  |
| `Stripe:` | From Stripe                            |
| `Auto:`   | Automated tag added by any integration |
| No prefix | Customer-managed tags                  |

The prefix lets the customer's team visually distinguish "tags my staff applied" from "tags some integration applied" — and lets your integration code filter for "my tags" when synchronizing back to the source.

***

## Capture, don't infer, source-of-record data

A frequent partner integration mistake: re-deriving source-of-record fields from data the integration doesn't authoritatively own. For example, computing a donor's giving total in your integration's database when Virtuous already calculates `lifeToDateGiving`.

The rule: **store what your platform owns; query what Virtuous owns.**

| Your platform owns                                                       | Virtuous owns                                                          |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| The donor's identity on your platform (Stripe Customer ID, your User ID) | The donor's identity inside Virtuous (Contact ID)                      |
| The specific donation event metadata (Stripe charge ID, payment method)  | The Gift record, its designations, premiums, and tax-deductible status |
| Your platform's tags, lists, segments                                    | The Contact's tags inside Virtuous                                     |
| Your platform's calculated metrics for your platform's purposes          | Virtuous's calculated metrics (giving totals, modification timestamps) |

When the customer wants a report that mixes both — "donors on the Mailchimp Gold list who gave more than \$1,000 last year" — pull the giving totals from Virtuous on demand rather than syncing them into your platform and risking staleness.

***

## Designate ownership of each field per integration

In a multi-integration customer environment, multiple systems may write to the same Contact. Without explicit field ownership, two integrations will overwrite each other's data.

The pattern: document **which fields each integration owns**, both within your integration's documentation and (ideally) in the customer's runbook for managing their Virtuous setup.

A typical breakdown for a fundraising-platform integration:

| Field                                | Owned by                           | Notes                                        |
| ------------------------------------ | ---------------------------------- | -------------------------------------------- |
| `name`, `firstName`, `lastName`      | Virtuous UI (the customer's staff) | Donor name corrections happen in Virtuous.   |
| `email`, `phone` (primary)           | Source platform                    | Updates flow in from the source.             |
| `email`, `phone` (additional)        | Virtuous UI                        | The customer's staff curates these.          |
| `tags` (with `MC:` prefix)           | Mailchimp integration              | Round-tripped via the Mailchimp recipe.      |
| `tags` (other)                       | Virtuous UI                        | Not touched by integrations.                 |
| `customFields["External Donor ID"]`  | Source platform                    | Set on creation, never modified.             |
| `customFields["Donor Segment Tier"]` | Virtuous UI                        | Customer staff sets this during cultivation. |

Your integration's code enforces this — outbound writes only modify fields you own. Inbound reads (webhook handlers) apply changes only to fields the source platform owns.

See [Build a Two-Way Sync — Pattern 2: per-field ownership](/crm/workflows/build-a-two-way-sync#pattern-2-per-field-ownership) for the implementation.

***

## Capture audit metadata at creation, not in narration

Several pieces of metadata are useful for diagnostics later: when did the integration first sync this record, which version of the integration code wrote it, what was the source event. The natural impulse is to put these in ContactNotes — but notes are designed for human-readable observations, not machine-readable diagnostics.

Better practice: capture diagnostic metadata as custom fields, not notes.

```javascript JavaScript theme={null}
{
  customFields: [
    { name: 'Integration Source', value: 'YourPlatform' },
    { name: 'Integration Version', value: '2.1.4' },
    { name: 'First Synced At', value: '2024-12-15T14:30:00Z' },
    { name: 'Source Event ID', value: 'evt_abc123' },
  ]
}
```

These fields are queryable, exportable, and don't clutter the Notes view that the customer's staff actually reads. Reserve ContactNotes for content that a human would want to see — interaction history, payment failures, manual flags.

***

## Use Relationships sparingly and only when configured

Virtuous Relationships connect two Contacts (donor and spouse, parent and child, donor and recruiting fundraiser). They're powerful but easy to misuse.

Two rules:

| Rule                                                                | Why                                                                                                                         |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Only create Relationships your integration genuinely owns.**      | Don't auto-create "Spouse" relationships from address sharing — let the customer's staff make those calls.                  |
| **Only create Relationships whose type is configured in Virtuous.** | Discovered via `GET /api/Relationship/Types`. If the customer hasn't configured the type, skip the relationship and log it. |

Relationships are a "two-way edge in the social graph" — a wrong relationship is more confusing than no relationship. When in doubt, capture the linkage in a custom field instead and let the customer's team formalize it as a Relationship if they want.

***

## Plan for the customer's reporting needs

The customer's team will eventually want to build reports against the data your integration writes. Three things make their work easier:

* **Consistent tags and custom field values.** "Major Donor" and "Major-donor" and "major\_donor" are three different segments to Virtuous Query, even though they're the same intent to a human.
* **Predictable Project codes.** The customer should be able to write `parameter: "Project", operator: "Is", value: "CLEAN-WATER"` and know it returns the right gifts. Cryptic codes (`P-2024-0317`) frustrate this.
* **Useful `originSegmentCode` values.** This field captures "how did this Contact first enter Virtuous?" — set it to a stable, descriptive value (`"FUNDRAISING-PLATFORM"`, `"STRIPE-DONATION"`, `"MAILCHIMP-SIGNUP"`) so reports can split donors by acquisition channel.

Confirm the customer's preferred values for these during onboarding. Document them in your integration's configuration so they're explicit, not buried in code.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="API Performance Tips" icon="gauge" href="/crm/best-practices/api-performance-tips">
    How to keep your integration fast and within rate limits at scale.
  </Card>

  <Card title="Error Recovery Patterns" icon="arrows-rotate" href="/crm/best-practices/error-recovery-patterns">
    The resilience patterns that complement good data modeling.
  </Card>

  <Card title="Custom Fields" icon="square-plus" href="/crm/concepts/custom-fields">
    The reference page for custom field reads and writes.
  </Card>

  <Card title="Funds, Campaigns, and Designations" icon="folder-tree" href="/crm/concepts/funds-campaigns-designations">
    The deeper reference for the Project hierarchy and designation structure.
  </Card>
</CardGroup>
