> ## 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.

# Custom Fields

> How nonprofits extend the CRM+ data model with organization-specific fields, and how partner integrations should discover, read, and write custom field values without hardcoding.

CRM+ allows each nonprofit organization to extend the standard data model with **custom fields** — typed, named fields configured per resource (Contacts, Gifts, Projects, and more). Custom fields store data that doesn't fit the standard schema: wealth screening scores, communication preferences, internal segmentation codes, partner-specific identifiers, and anything else the organization needs.

For partner integrations, custom fields are the supported extension point — and the right way to store partner-specific metadata on Virtuous records. This page covers how to discover custom fields configured for an organization, how to read and write their values, and the design patterns that keep your integration robust against per-customer configuration differences.

## How custom fields work

Each resource type has its own independent custom field configuration. A nonprofit can have ten custom fields on Contacts, three on Gifts, and zero on Projects — every resource is configured separately. The configuration is **per-organization**, not platform-wide: every nonprofit customer of yours may have a different set of custom fields enabled.

This has one critical implication for partners:

<Warning>
  Never hardcode custom field names in your integration. The field named `"wealthScore"` in one nonprofit's Virtuous organization may not exist in another's, may be named differently, or may have a different data type. Discover the available custom fields at integration setup (and periodically thereafter), and present your customer with a mapping UI rather than assuming a schema.
</Warning>

***

## Resources that support custom fields

| Resource             | Discovery endpoint                           |
| -------------------- | -------------------------------------------- |
| Contact              | `GET /api/Contact/CustomFields`              |
| ContactIndividual    | `GET /api/ContactIndividual/CustomFields`    |
| ContactNote          | `GET /api/ContactNote/CustomFields`          |
| Gift                 | `GET /api/Gift/CustomFields`                 |
| GiftAsk              | `GET /api/GiftAsk/CustomFields`              |
| RecurringGift        | `GET /api/RecurringGift/CustomFields`        |
| Project              | `GET /api/Project/CustomFields`              |
| Pledge               | `GET /api/v2/Pledge/CustomFields`            |
| PlannedGift          | `GET /api/PlannedGift/CustomFields`          |
| Premium              | `GET /api/Premium/CustomFields`              |
| Event                | `GET /api/Event/CustomFields`                |
| EventAttendee        | `GET /api/EventAttendee/CustomFields`        |
| Grant                | `GET /api/Grant/CustomFields`                |
| VolunteerOpportunity | `GET /api/VolunteerOpportunity/CustomFields` |

Each endpoint returns the same shape — the set of custom fields configured for that resource type in the current organization.

***

## Discovering available fields

`GET /api/Contact/CustomFields` (and its siblings) returns an array describing every custom field configured on the resource:

```json theme={null}
[
  {
    "name": "wealthScore",
    "displayName": "Wealth Score",
    "dataType": "Number",
    "options": []
  },
  {
    "name": "preferredContactMethod",
    "displayName": "Preferred Contact Method",
    "dataType": "Dropdown",
    "options": ["Email", "Phone", "Mail", "Text Message"]
  },
  {
    "name": "boardMemberSince",
    "displayName": "Board Member Since",
    "dataType": "Date",
    "options": []
  },
  {
    "name": "isNewsletterSubscriber",
    "displayName": "Newsletter Subscriber",
    "dataType": "Boolean",
    "options": []
  }
]
```

| Field         | Description                                                                                                 |
| ------------- | ----------------------------------------------------------------------------------------------------------- |
| `name`        | The internal field name. Use this in `customFields[].name` when reading or writing values on a record.      |
| `displayName` | The label shown in the Virtuous UI. Use this when presenting the field to your customer for mapping.        |
| `dataType`    | The data type — drives how to serialize the value when writing. See [Data types](#data-types) below.        |
| `options`     | For `Dropdown` (and similar enum-style) fields, the list of valid values. Empty array for free-form fields. |

<Tip>
  Call the appropriate `/CustomFields` endpoint at integration setup and cache the result. Re-fetch periodically (daily is reasonable) to pick up new fields the customer has added. Don't fetch on every record read — the schema does not change often enough to justify the request cost.
</Tip>

***

## Data types

The CRM+ spec types every custom field value as `string` — both on read and on write. The live API accepts and returns the natural representation for each `dataType`, but partner integrations need to know which serialization to use when sending values.

| `dataType` | What it stores             | Wire format on write                                                                                                                    |
| ---------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `Text`     | Free-text strings          | String, e.g., `"Major Donor"`.                                                                                                          |
| `Number`   | Numeric values             | String containing the number, e.g., `"850"` (the spec types it as string; the value is treated as numeric internally).                  |
| `Date`     | Calendar dates             | ISO 8601 date string, e.g., `"2024-12-15"`.                                                                                             |
| `Boolean`  | True/false flags           | The string `"true"` or `"false"`.                                                                                                       |
| `Dropdown` | One value from a fixed set | One of the strings listed in the field's `options` array. The value must match exactly — `"Email"` is valid; `"email"` may be rejected. |

<Warning>
  The CRM+ spec types every custom field value as `type: string` regardless of the underlying `dataType` . When **writing** values, follow the wire format in the table above. When **reading** values, parse the string into the natural type based on the field's `dataType` from the discovery endpoint.
</Warning>

***

## Reading custom field values

Custom field values appear on the resource record as a `customFields` array. Each entry references the field by `name` and carries its current value:

```json theme={null}
{
  "id": 4821,
  "name": "Wayne, Bruce",
  "customFields": [
    { "name": "wealthScore", "value": "850", "displayName": "Wealth Score" },
    { "name": "preferredContactMethod", "value": "Email", "displayName": "Preferred Contact Method" },
    { "name": "boardMemberSince", "value": "2018-03-15", "displayName": "Board Member Since" },
    { "name": "isNewsletterSubscriber", "value": "true", "displayName": "Newsletter Subscriber" }
  ]
}
```

To read a specific custom field value:

```javascript theme={null}
function getCustomField(record, fieldName) {
  const field = (record.customFields || []).find((f) => f.name === fieldName);
  return field?.value;
}

// Usage
const score = getCustomField(contact, 'wealthScore');
const subscribedRaw = getCustomField(contact, 'isNewsletterSubscriber');
const subscribed = subscribedRaw === 'true';
```

If a record has no value for a custom field, the field may either be absent from the `customFields` array or present with an empty `value`. Always default safely on missing values.

***

## Writing custom field values

When creating or updating a record, include the `customFields` array with the fields you want to set. Only the fields you include are written — fields you omit are unchanged (consistent with the PUT-as-PATCH behavior covered on the [Contacts](/crm/concepts/contacts#updating-contacts) page).

```bash cURL theme={null}
curl -X PUT https://api.virtuoussoftware.com/api/Contact/4821 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customFields": [
      { "name": "wealthScore", "value": "920" },
      { "name": "preferredContactMethod", "value": "Email" }
    ]
  }'
```

A few constraints to respect:

* **The `name` field must match the configured field name exactly.** Use the values from the discovery endpoint, not the display names. `"wealthScore"` is correct; `"Wealth Score"` is not.
* **Dropdown values must match one of the configured options exactly** (case-sensitive). The discovery endpoint returns the valid options for each Dropdown field — your integration should validate against that list before submitting.
* **Sending an empty `value` clears the field.** If you want to remove a value, send `{ "name": "fieldName", "value": "" }`. Omitting the field entirely from the `customFields` array leaves it unchanged.

<Tip>
  When syncing custom field values from your platform, treat unknown field names as warnings rather than errors. A custom field the customer expected may have been renamed or removed in their Virtuous configuration; your integration should log the issue and continue rather than failing the entire sync.
</Tip>

***

## Custom Collections — the multi-instance variant

Some data doesn't fit a single-value custom field — for example, "multiple board memberships, each with start date and role." For these cases CRM+ supports **Custom Collections**, which are configurable multi-instance sub-records on a Contact or ContactIndividual.

A Custom Collection has:

* A name (e.g., "Board Memberships").
* A set of fields (each with a name, displayName, dataType, and optional options).
* Multiple **collection instances** per record — each instance is one row of data in the collection.

### Discovering Custom Collections

| Endpoint                                       | Use                                                       |
| ---------------------------------------------- | --------------------------------------------------------- |
| `GET /api/Contact/CustomCollections`           | List Custom Collections configured on Contacts.           |
| `GET /api/ContactIndividual/CustomCollections` | List Custom Collections configured on ContactIndividuals. |

The response includes each collection's `id`, `name`, and `fields` array. The fields array has the same shape as standard custom fields — each field has a name, displayName, dataType, and options.

### Reading Custom Collection values

Custom Collection instances appear on the record as a `customCollections` array, with each entry containing the collection's metadata and a `fields` array of name/value pairs for that instance:

```json theme={null}
{
  "customCollections": [
    {
      "customCollectionId": 7,
      "customCollectionName": "Board Memberships",
      "collectionInstanceId": 1001,
      "fields": [
        { "name": "role", "value": "Director" },
        { "name": "startDate", "value": "2018-03-15" }
      ]
    },
    {
      "customCollectionId": 7,
      "customCollectionName": "Board Memberships",
      "collectionInstanceId": 1002,
      "fields": [
        { "name": "role", "value": "Treasurer" },
        { "name": "startDate", "value": "2022-09-01" }
      ]
    }
  ]
}
```

### Writing Custom Collection instances

`POST /api/Contact/{contactId}/Collection/{customCollectionId}` creates a new instance on a Contact. The request body provides the field values for the new instance. Other endpoints update or delete individual instances by `collectionInstanceId`.

<Note>
  Custom Collections are a more advanced feature than standard Custom Fields and are used by a smaller subset of nonprofits. Most partner integrations only need to read and write standard Custom Fields. If your customer has a use case that requires multi-instance data on a single record, Custom Collections are the right tool — but reach out to your Virtuous partner contact for setup guidance, because the configuration is typically managed in close coordination with the nonprofit's Virtuous administrator.
</Note>

***

## Partner integration patterns

### Pattern 1: Custom fields as a partner-side write target

Use custom fields to write partner-specific data into Virtuous records — for example, a Stripe customer ID, a Mailchimp subscription status, an engagement score from your platform. The discovery endpoint lets your customer's Virtuous administrator configure the fields once, and your integration reads/writes them by name.

This is the cleanest pattern for storing your platform's metadata on Virtuous records without polluting the standard schema.

### Pattern 2: Custom fields as a partner-side read source

Some partner integrations need to read custom field values that the nonprofit maintains in Virtuous — for example, a marketing automation tool that uses a "wealthScore" custom field to segment outreach. Use the discovery endpoint plus the Contact read endpoints, and surface a mapping UI to your customer so they can point your integration at the right custom fields.

### Pattern 3: Custom fields as a configuration channel

For more advanced integrations, custom fields can carry per-record configuration that affects how your integration treats the record — for example, a `excludeFromAutomation` Boolean field that suppresses your platform's actions for that donor. This pattern requires careful customer onboarding (the nonprofit must configure the field and populate it), but it offers a customer-controllable extension point without requiring code changes in your integration.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Contacts" icon="user" href="/crm/concepts/contacts">
    The Contact resource — the most common target for custom field reads and writes.
  </Card>

  <Card title="Donations / Gifts" icon="hand-holding-dollar" href="/crm/concepts/gifts">
    Gifts have their own custom field schema — separate from Contacts.
  </Card>

  <Card title="Relationships and IDs" icon="link" href="/crm/concepts/relationships-and-ids">
    The complementary pattern — using `contactReferences` for cross-system identifiers.
  </Card>

  <Card title="Statuses and Lifecycle States" icon="circle-half-stroke" href="/crm/concepts/statuses-and-lifecycle-states">
    Boolean custom fields are often used as ad-hoc lifecycle flags.
  </Card>
</CardGroup>
