Contacts

Store customers and companies with tags, segments, custom fields, and links to plugin records.

For custom attributes, see Properties, Tags & Segments. For bulk loads, see Import & Export.

Module layout

backend/core-api/src/modules/contacts/
  graphql/schemas/{customer,company}.ts
  graphql/resolvers/{queries,mutations,customResolvers}/
  db/definitions/{customers,company}.ts
  db/models/{Customers,Companies}.ts
  trpc/{customer,company,index}.ts      # service-to-service access
  meta/activity-log/{customers,companies}/
  meta/import-export/{import,export}/   # CSV handlers per entity
  constants.ts, utils.ts, @types/customer.ts

Linking modules alongside it: modules/conformities/ (legacy type-pair links) and modules/relations/ (generic entity-to-entity edges). Frontend lives in frontend/core-ui/src/modules/contacts/{customers,companies,client-portal-users} with shared pickers in frontend/libs/ui-modules/src/modules/contacts/.

Data model

Customer (customers.ts):

Field Notes
state visitor by default; promoted to lead/customer
firstName, lastName, middleName, primaryEmail, emails, primaryPhone, phones, code Identity and contact points
emailValidationStatus, phoneValidationStatus unknown until verified
visitorContactInfo, trackedData, location Visitor/session capture
leadStatus, status, isSubscribed, ownerId, position, department Pipeline and ownership
tagIds, customFieldsData, propertiesData, links Labels and custom data
searchText, searchTokens Maintained by the search token config (below)

Company (db/definitions/company.ts): primaryName, names, code, size, industry, businessType, website, primaryEmail, primaryPhone, addresses, parentCompanyId, score, doNotDisturb, mergedIds, plus the same tagIds, customFieldsData, propertiesData, trackedData, ownerId, status pattern.

Search tokens

customerSearchTokenConfig in db/definitions/customers.ts controls partial-match search. Each entry names a path, a mode (prefix, word-prefix, exact), and a minLength:

Path Mode Min length
firstName, middleName, lastName prefix 2
primaryEmail, emails word-prefix 3
primaryPhone, phones word-prefix 1
code exact 1
visitorContactInfo.email word-prefix 3
visitorContactInfo.phone word-prefix 1

Tokens are materialized into searchTokens (indexed with state and status), so searchValue filters stay MongoDB queries. Inputs shorter than minLength do not match, so a too-short search can look like it finds nothing.

GraphQL API

Customers (graphql/schemas/customer.ts)

Operation Key arguments
customers searchValue, type, ids, excludeIds, tagIds, excludeTagIds, tagWithRelated, segment, segmentIds, brandIds, integrationIds, integrationTypes, clientPortalId, formIds, leadStatus, status, sex, birthDate, dateFilters, propertiesData, emailValidationStatus, sortField, sortDirection, autoCompletion, autoCompletionType, startDate, endDate, conformity args, cursor params; returns CustomersListResponse
customerDetail _id!
customersCount types: [CUSTOMER_RELATION_TYPE]
contactsLogs action, content, contentType
cpCustomers / cpCustomerDetail client-portal variants
customersAdd / customersEdit state, firstName, lastName, primaryEmail, emails, primaryPhone, phones, primaryAddress, addresses, ownerId, position, department, leadStatus, description, links, code, sex, birthDate, avatar, isSubscribed, propertiesData, validation statuses
customersMerge customerIds, customerFields
customersRemove customerIds
customersChangeState / customersChangeStateBulk _id!, value! / _ids: [String!]!, value!
customersVerify, customersChangeVerificationStatus verificationType! / customerIds, type!, status!

Companies (graphql/schemas/company.ts)

Operation Key arguments
companies same selector family as customers; returns CompaniesListResponse
companyDetail _id!
companiesAdd / companiesEdit primaryName, names, avatar, size, industry, businessType, website, primaryEmail, emails, primaryPhone, phones, primaryAddress, addresses, parentCompanyId, code, location, ownerId, tagIds, propertiesData, links, isSubscribed
companiesMerge companyIds, companyFields
companiesRemove companyIds

Linking records

  • Conformities (modules/conformities): conformityAdd, conformityEdit mutations store main-type/rel-type pairs. Filter contacts with conformityMainType, conformityMainTypeId, conformityRelType, conformityIsRelated, conformityIsSaved on the list queries.
  • Relations (modules/relations): generic entities: [{ contentType, contentId }] edges. createRelation, manageRelations, getRelationsByEntity let plugins link contacts to deals, tickets, or their own types without touching the contact document.

List queries return a cursor envelope { list, pageInfo { hasNextPage, endCursor }, totalCount }, controlled by limit, cursor, cursorMode, direction, orderBy.

Example

query CustomersByTag($tagIds: [String], $cursor: String) {
  customers(tagIds: $tagIds, cursor: $cursor, limit: 20) {
    list {
      _id
      firstName
      primaryEmail
      tagIds
      customFieldsData
      cursor
    }
    totalCount
    pageInfo { hasNextPage endCursor }
  }
}

mutation AddCustomer {
  customersAdd(
    firstName: "Ari"
    primaryEmail: "[email protected]"
    primaryPhone: "+15551234567"
    code: "CUST-001"
  ) { _id state }
}

tRPC access

Other services reach contacts through sendTRPCMessage({ pluginName: 'core', module: 'customers', action: ... }). Procedures on trpc/customer.ts include find, findOne, findActiveCustomers, getCustomerName, getWidgetCustomer, count, createCustomer, updateCustomer, updateOne, updateMany, removeCustomers, markCustomerAsActive, createMessengerCustomer, updateMessengerCustomer, saveVisitorContactInfo, updateLocation, updateSession, setUnsubscribed, createOrUpdate, tag. The company router mirrors the core CRUD set.

Permissions

Mutations check core actions from meta/permissions.ts: contactsCreate, contactsUpdate, contactsDelete, contactsMerge, customersImportManage, customersExportManage, companiesImportManage, companiesExportManage. contactsRead is marked always: true and supports scopes own (records in createdBy/ownerId) and all.

Operate on one system.

Talk to our team about consolidating your stack onto erxes. 30-minute walkthrough, custom to your org.