# Properties, Tags & Segments

Custom fields, labels, and dynamic audiences


Model custom fields and system-field visibility, label records with tags, and build dynamic audiences with segments.

For bulk changes, see [Import & Export](https://erxes.io/docs/core-import-export).

## Properties

`backend/core-api/src/modules/properties/`:

```text
db/definitions/{common,field,group,systemField}.ts
db/models/{Field,Group,SystemField}.ts
graphql/schemas/{field,group,property,systemField,index}.ts
graphql/resolvers/{queries,mutations,customResolvers}/
trpc/{field,group,index}.ts
```

A `Field` ([field.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/core-api/src/modules/properties/db/definitions/field.ts)) defines `name`, `code`, `groupId`, `contentType`, `contentTypeId`, `type`, `order`, `options[]`, `logics`, `validations`, `configs`, `icon`, `isVisible`, `isVisibleToCreate`, `isRequired`, `isVisibleInCard`. A `FieldGroup` orders fields per content type. Values written to records land in `customFieldsData`/`propertiesData` as structured JSON.

| Operation | Key arguments |
| --------- | ------------- |
| `fields` | `params: { contentType, contentTypeId, groupId, cursor params }`; returns `FieldListResponse` |
| `fieldDetail` | `_id!` |
| `fieldAdd` / `fieldEdit` / `fieldRemove` | `name`, `code`, `groupId`, `contentType`, `contentTypeId`, `type`, `options`, `validations`, `logics`, `configs`, `icon`, `isVisible`, `isVisibleToCreate`, `isRequired`, `isVisibleInCard`, `order` |
| `fieldGroups` | `params: { contentType, contentTypeId, cursor params }` |
| `fieldGroupAdd` / `fieldGroupEdit` / `fieldGroupRemove` | group fields / `_id!` |
| `fieldGroupsUpdateOrder` | `orders: [FieldGroupOrderItem!]!` |
| `propertyTypes` | none; returns every registered content type |
| `propertySystemFields` | `contentType!` |
| `propertySystemFieldEdit` | `contentType!`, `code!`, `isVisible`, `isVisibleToCreate`, `isRequired`, `logics` |
| `cpFields`, `cpFieldDetail`, `cpFieldGroups` | client-portal variants |

### System fields ("Basic information")

Each plugin can declare built-in `systemFields` inside `meta/properties.ts` `types[]` (core declares them for `customer`, `company`, `product`, `user`; for example customer `firstName`, `primaryEmail`, `ownerId`, `tagIds`). `propertySystemFields` merges the declaration with tenant overrides stored in the `SystemFieldSettings` collection ([db/models/SystemField.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/core-api/src/modules/properties/db/models/SystemField.ts)), and `propertySystemFieldEdit` persists `isVisible`, `isVisibleToCreate`, `isRequired`, and `logics` (operators `is`/`isNot`, actions `show`/`hide`). Settings → Properties renders them as the "Basic information" section (`frontend/core-ui/src/modules/properties/components/record/PropertiesSystemFieldsSection.tsx`).

## Tags

`backend/core-api/src/modules/tags/` (`graphql/schemas.ts`, `queries.ts`, `mutations.ts`, `db/definitions/tags.ts`, `db/models/Tags.ts`, `taggable.ts`, `trpc/tag.ts`).

A `Tag` has `name` (unique), `colorCode`, `parentId`, `relatedIds`, `isGroup`, `type` (the content type it applies to), `description`, `objectCount`, `order`. `taggable.ts` looks up the target model from the tag's `type` so any entity can be tagged.

| Operation | Key arguments |
| --------- | ------------- |
| `tagsGetTypes` | none; returns `meta.tags.types` from every plugin |
| `tags` | `type`, `searchValue`, `parentId`, `ids`, `excludeIds`, `isGroup`, `instanceId`, `includeWorkspaceTags`, `sortField`, cursor params; returns `TagsListResponse` |
| `tagsMain` / `tagDetail` / `tagsQueryCount` | `type`, `excludeWorkspaceTags` / `_id!` / `type`, `searchValue` |
| `tagsAdd` / `tagsEdit` | `name!` plus `type`, `colorCode`, `parentId`, `isGroup`, `description` |
| `tagsTag` | `type!`, `targetIds: [String!]!`, `tagIds: [String!]!` |
| `tagsRemove` | `_id!` |
| `cpTags`, `cpTagsAdd`, `cpTagsTag` | client-portal variants |

Tag types are declared per plugin in `meta/tags.ts` (`tags.types`; core registers `customer`, `company`, `product`, `user`, `form`, `automation`, `documents`). A tag only applies to records whose content type matches its `type`; `tagsGetTypes` shows the registry. Use tags for operator-driven labels, not for access control.

## Segments

`backend/core-api/src/modules/segments/`:

```text
graphql/schemas/index.ts, resolvers/{queries,mutations,customResolvers}
db/definitions/{segments,segmentNodes,segmentHistory}.ts
db/models/{Segments,SegmentHistory}.ts
trpc/{index,segments}.ts
utils/{access,publishBuild,relationEdges,runSegment,segmentGateway}.ts
```

A `Segment` ([segments.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/core-api/src/modules/segments/db/definitions/segments.ts)) has `contentType`, `name`, `ownedBy`, `description`, `color`, `root` (the condition tree), `dependsOn`, `fingerprint`, `visibility` (`private`/`organization`), `ownerId`, `status` (`draft`/`building`/`active`/`failed`/`cancelled`), `revision`, `membersCount`, `membersCountedAt`, `timeSensitive`, and `build*` progress fields (`buildStartedAt`, `buildProcessed`, `buildTotal`, `buildCancelRequested`). `SegmentHistory` stores per-day membership (`count`, `joined`, `left`) for `segmentGrowth`.

| Operation | Key arguments |
| --------- | ------------- |
| `segmentsGetTypes` | none; returns registered content types |
| `segments` / `segmentDetail` | `contentTypes: [String]!`, `ids`, `excludeIds`, `searchValue` / `_id!` |
| `segmentFields` | `contentType!`; returns filterable fields incl. custom properties |
| `segmentRelations` | `subjectType!`; returns relations the segment can traverse |
| `segmentsPreviewCount` | `contentType!`, `root!`; live count for the form |
| `segmentSameDefinition` | `contentType!`, `root!`, `excludeId`; duplicate detection |
| `segmentMembers` / `segmentMemberCount` | `segmentId!`, `cursor`, `limit` |
| `segmentGrowth` | `segmentId!`, `days` |
| `segmentUsage` | `ids: [String!]!`; returns automations and segments referencing it |
| `segmentsAdd` / `segmentsEdit` | `contentType!`, `name`, `description`, `color`, `root!`, `visibility`, `status`, `ownedBy` |
| `segmentsRemove` | `ids: [String!]!` |
| `segmentsRebuild` / `segmentsStopRebuild` | `_id!` |

### How evaluation runs

Evaluation is **MongoDB**, not Elasticsearch. `erxes-api-shared/src/core-modules/segments/` translates the `root` node tree into a `SegmentMongoFilter` (`mongoFilter.ts`, `evaluate.ts`, `memberQuery.ts`, `evaluateBatch.ts`, `measureRelations.ts`). The segmentation worker runs in the **logs service** ([segmentWorker.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/services/logs/src/bullmq/segments/segmentWorker.ts)) with four BullMQ queues (`changed`, `forget`, `rebuild`, `reconcile`), each with a `SEGMENT_CONCURRENCY_*` env override. Membership changes are applied per plugin through `applyMembership` producers and logged into `SegmentHistory`.

> [!WARNING]
> **Segments depend on the logs service**
>
> A segment stuck in `building` usually means the logs service is not running; check `SEGMENT_CONCURRENCY_REBUILD` and the service logs (`segmentsStopRebuild` cancels a build). If a count comes back lower than expected, `SegmentMemberCount.unsupported` lists tree nodes the Mongo filter could not express and `exceeded` means evaluation bailed early.

Plugins register segment content types through `meta/segments` passed to `startPlugin` (or `initSegmentProducers` for core): `contentTypes`, `segmentFields`, `segmentFieldNamespaces`, `segmentRelations`, plus `evaluateFields`, `listSegmentMembers`, `countSegmentMembers`, `applyMembership` handlers. Core registers `core:organization.users`, `core:contacts.companies`, `core:contacts.customers`, `core:contacts.leads`, `core:products.products` in `meta/segments/contentTypes.ts`; sales registers its deal/pos types the same way.

## Permissions

From `meta/permissions.ts`: `fieldsManage`, `fieldGroupsManage` (properties, read is `always: true`); `tagsCreate`, `tagsUpdate`, `tagsDelete`, `tagsTag` (`tagsRead` always); `segmentsManage` (`segmentsRead` always). All use scope `all`.

