Skip to main content
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:
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.

Resources that support custom fields

ResourceDiscovery endpoint
ContactGET /api/Contact/CustomFields
ContactIndividualGET /api/ContactIndividual/CustomFields
ContactNoteGET /api/ContactNote/CustomFields
GiftGET /api/Gift/CustomFields
GiftAskGET /api/GiftAsk/CustomFields
RecurringGiftGET /api/RecurringGift/CustomFields
ProjectGET /api/Project/CustomFields
PledgeGET /api/v2/Pledge/CustomFields
PlannedGiftGET /api/PlannedGift/CustomFields
PremiumGET /api/Premium/CustomFields
EventGET /api/Event/CustomFields
EventAttendeeGET /api/EventAttendee/CustomFields
GrantGET /api/Grant/CustomFields
VolunteerOpportunityGET /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:
[
  {
    "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": []
  }
]
FieldDescription
nameThe internal field name. Use this in customFields[].name when reading or writing values on a record.
displayNameThe label shown in the Virtuous UI. Use this when presenting the field to your customer for mapping.
dataTypeThe data type — drives how to serialize the value when writing. See Data types below.
optionsFor Dropdown (and similar enum-style) fields, the list of valid values. Empty array for free-form fields.
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.

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.
dataTypeWhat it storesWire format on write
TextFree-text stringsString, e.g., "Major Donor".
NumberNumeric valuesString containing the number, e.g., "850" (the spec types it as string; the value is treated as numeric internally).
DateCalendar datesISO 8601 date string, e.g., "2024-12-15".
BooleanTrue/false flagsThe string "true" or "false".
DropdownOne value from a fixed setOne of the strings listed in the field’s options array. The value must match exactly — "Email" is valid; "email" may be rejected.
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.

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:
{
  "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:
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 page).
cURL
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.
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.

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

EndpointUse
GET /api/Contact/CustomCollectionsList Custom Collections configured on Contacts.
GET /api/ContactIndividual/CustomCollectionsList 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:
{
  "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.
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.

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

Contacts

The Contact resource — the most common target for custom field reads and writes.

Donations / Gifts

Gifts have their own custom field schema — separate from Contacts.

Relationships and IDs

The complementary pattern — using contactReferences for cross-system identifiers.

Statuses and Lifecycle States

Boolean custom fields are often used as ad-hoc lifecycle flags.
Last modified on May 27, 2026