---
id: CON-CRM-ENT-007
name: Contact Type
feature: FEAT-CRM-ENT-06
product_area: crm
module: entities
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
definition: A contact type classifies people using the workspace’s own categories.
owned_by: MOD-CRM-ENT
facets: []
evidence:
  - >-
    `services-crm` at `7ea48a414c42eade10d60753b8bcc323b1dd726f`:
    `modules/entities/db/contact-types.table.ts` records this concept.
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Why It Exists
    provenance: inferred
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers 400 for a contact
      with neither a first nor a last name, naming the rule;
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#builds the full name from
      every part in order, trimmed and singly spaced;
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers 400 for a full name
      sent instead of name parts, and never splits it into parts;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      an honorific salutation to the prefix, start-cased and dotted (RB-2367);
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#names
      every overlong name part of one request at once;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the id the body named,
      which is no UUIDv7;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#keeps
      the number consumed when the transaction rolls back: a gap, never given back;
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409 for a type the tenant
      does not have, and saves no contact;
      services-crm:modules/entities/tests/integration/db/contacts.table.integration.spec.js#refuses
      a status outside the closed list by contacts_status_check;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.spouse.integration.spec.js#links
      the other spouse back when a contact is created naming it;
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0042.spec.ts#CRM-ACC-0042;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for a photo whose
      upload is not there, naming the field that failed;
      services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#answers 400 naming the field
      for a create whose name is nothing but spaces;
      services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#answers 400 for an id that
      is not a configuration id, saying what an id may hold;
      services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#says which records keep the
      type when it refuses the delete;
      services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#answers 400 for a reorder
      that leaves a type of the tenant out;
      services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#answers only the types
      usable on organizations when the query asks for them;
      services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#answers 403 to a read-only
      caller’s reorder; services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#then, when
      the version is off, refuses the change (409);
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0033.spec.ts#CRM-ACC-0033;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#is not answered the section
      it may not read: it is ABSENT, not null, so "forbidden" and "unset" never look alike;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#writes NEITHER section for
      that refused save: the decision is made before anything is written;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#refuses a save from a stale
      version (409);
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js#refuses
      a definition of the wrong kind: a console form in the console page layout slot;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#KEEPS all five through a PUT
      that does not mention them: an older client cannot wipe a tenant’s assignments;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#falls back to the tenant’s
      default for a kind the type leaves empty;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#is refused the tenant’s
      defaults (403): reading a record does not make one an administrator of layouts
    note: Business need inferred from the sourced behavior; no historical motivation is attributed.
  - section: What It Controls
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers 400 for a contact
      with neither a first nor a last name, naming the rule;
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#builds the full name from
      every part in order, trimmed and singly spaced;
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers 400 for a full name
      sent instead of name parts, and never splits it into parts;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      an honorific salutation to the prefix, start-cased and dotted (RB-2367);
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#names
      every overlong name part of one request at once;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the id the body named,
      which is no UUIDv7;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#keeps
      the number consumed when the transaction rolls back: a gap, never given back;
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409 for a type the tenant
      does not have, and saves no contact;
      services-crm:modules/entities/tests/integration/db/contacts.table.integration.spec.js#refuses
      a status outside the closed list by contacts_status_check;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.spouse.integration.spec.js#links
      the other spouse back when a contact is created naming it;
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0042.spec.ts#CRM-ACC-0042;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for a photo whose
      upload is not there, naming the field that failed;
      services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#answers 400 naming the field
      for a create whose name is nothing but spaces;
      services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#answers 400 for an id that
      is not a configuration id, saying what an id may hold;
      services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#says which records keep the
      type when it refuses the delete;
      services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#answers 400 for a reorder
      that leaves a type of the tenant out;
      services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#answers only the types
      usable on organizations when the query asks for them;
      services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#answers 403 to a read-only
      caller’s reorder; services-crm:modules/entities/tests/e2e/contact-types.e2e.spec.js#then, when
      the version is off, refuses the change (409);
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0033.spec.ts#CRM-ACC-0033;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#is not answered the section
      it may not read: it is ABSENT, not null, so "forbidden" and "unset" never look alike;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#writes NEITHER section for
      that refused save: the decision is made before anything is written;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#refuses a save from a stale
      version (409);
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js#refuses
      a definition of the wrong kind: a console form in the console page layout slot;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#KEEPS all five through a PUT
      that does not mention them: an older client cannot wipe a tenant’s assignments;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#falls back to the tenant’s
      default for a kind the type leaves empty;
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#is refused the tenant’s
      defaults (403): reading a record does not make one an administrator of layouts
---

# Contact Type

## What It Is

A Contact Type is a workspace category for people, such as Member or Prospect. It gives staff a common classification without changing the fact that each contact represents one person. Authorized users create the catalogue definition with a name and stable configuration identity, and individual contacts can select it or remain untyped.

The type can also name Platform definitions for presentation. Its assignments help resolve the forms and layouts appropriate to that category, allowing a member entry task to differ from another kind of person record without building a separate person model.

## Why It Exists

Our reading is that a workspace needs its own vocabulary for people while retaining a shared contact foundation. Staff can classify a person consistently and choose presentation appropriate to that category. Putting the classification in a reusable definition avoids copying the category's configuration into every person and keeps a later terminology or presentation adjustment distinct from a person's identity.

## What It Is Not

A Contact Type is not contact status, membership eligibility, or a security role. A contact classified as Member does not acquire a membership record or permission merely through that label. The type is also not a form or layout definition: Platform metadata owns those objects, and CRM selects them through validated assignments. A person without a type remains a contact.

## What It Controls

The type identifies the person's classification and can override the workspace's presentation assignments for individual form and layout slots. Effective resolution can choose a type-specific form or layout while falling back to defaults for slots the type leaves empty. This affects which configured presentation staff receive, including the page layout that organizes record cards.

Active catalogue choices shape contact creation and sole-choice preselection. An existing contact retains an inactive classification, and service reference validation differs from the chooser's active-only policy. Used types resist deletion, so deactivation can retire new selection without stripping meaning from existing records. Catalogue order affects the shared list rather than the person's identity.

## How It Is Used

The CRM Administrator configures the classification and its presentation assignments. The CRM Editor encounters active choices at creation and uses the resolved presentation when working with contacts. Create a member record and inspect the saved information begins with that choice. Staff should inspect the person's actual business records before treating a category as evidence of entitlement or standing that another application owns.

## Example

An association defines Member and Prospect contact types, giving Member a tailored edit form and leaving its page-layout slot at the workspace default. A coordinator creates Ruth as Member and receives those effective choices. Later Prospect is deactivated: existing prospects keep their classification, but new chooser visits omit it. No person's history or membership rights are rewritten simply because a catalogue choice changes.

## How It Relates

Contact Type classifies Contact and uses CRM Settings as the fallback source for presentation assignments. Platform metadata owns forms, layouts, and the card model used by presentation. Organization Type and Affiliation Type classify different ideas, and Contact Role describes responsibilities. Record Protection and History constrains catalogue mutations where locks apply; membership records remain owned by the Membership application.
