Skip to main content

Data model reference

The data model screen, where entity types and their attributes are defined.

Attribute settings

Every field an attribute definition accepts. Changeable answers the question that costs the most to get wrong — whether you can still alter this after records exist.

FieldAcceptsDefaultChangeableWhat it does
entityTypeIdThe entity type's identifierNoThe type this attribute belongs to. Moving an attribute between types is not an edit; it is a new attribute.
name1–100 chars, letters, digits, underscore; must start with a letterNoThe identifier used by matching rules, validation rules, and the API. Immutable because every rule that names it would otherwise break silently.
slug1–100 chars, lowercase, digits, hyphenDerived from the nameNoURL-safe identifier.
displayName1–255 charsYesThe human label shown throughout the console. Safe to change at any time.
descriptionUp to 2,000 charsYesGuidance for whoever fills this in.
dataTypeOne of the 12 types belowYesThe value's shape. See the note on promotion below.
subFields1–40 sub-fieldsNoThe named parts of a composite. Required when the type is composite, and fixed once created.
rdmLookupTypeIdA lookup typeYesConstrains values to that list's codes. Required when the type is lookup.
rdmHierarchyIdA hierarchyYesBinds values to a level of a leveled hierarchy. Moves together with the level below.
rdmHierarchyLevelOrderInteger, 1 or greaterYesWhich level of that hierarchy the value sits at.
valueTypeLookupIdA lookup typeYesSupplies the per-value usage types (home, work, mobile). Distinct from rdmLookupTypeId: that one constrains the value, this one constrains the usage type attached to each value.
isRequiredtrue or falsefalseYesA required attribute blocks record creation when absent.
isUniquetrue or falsefalseYesNo two records of this type may share a value.
isSearchabletrue or falsetrueYesRecorded intent only — see the note below.
isPiitrue or falsefalseYesRecorded intent only — see the note below.
defaultValueAny value of the declared typeYesApplied when a record arrives without one.
displayOrderInteger, 0 or greater0YesOrdering within its group on entity screens and forms.
attributeGroup1–100 chars, free textYesThe named section this appears under. Free text rather than a fixed list, so groups emerge per tenant.
maxCardinalityInteger, 0 or greater1YesHow many values one record may hold. See the warning below.
metadataArbitrary object{}YesYour own annotations. The platform stores and returns them without interpreting them.

Data types

string · number · integer · decimal · boolean · date · datetime · email · phone · json · lookup · composite

A type can be promoted in place

dataType is editable, which is not what most schema systems allow. The intended use is promotion: an attribute that started as free-text string can become a lookup bound to a controlled list once you have the reference data for it — a diagnosis code moving from typed-in text to a bound clinical terminology, for instance.

The binding rules are re-checked against the resulting state, so a promotion that would leave the attribute inconsistent is refused at that moment.

Lowering maxCardinality does not remove existing values

The limit is enforced when a value is written, not retroactively. Lower it from 3 to 1 and records that already hold three values keep all three; only subsequent writes are rejected.

So a reduction is not a cleanup. If you need the extra values gone, remove them deliberately — otherwise the model claims single-value while the data is not.

isSearchable and isPii do not yet change behaviour

Both flags are stored on the definition and returned by the API, and neither is currently acted upon.

  • Turning isSearchable off does not remove the attribute from attribute-scoped search. Search resolves attributes by name; it never consults this flag.
  • Setting isPii does not mask, redact, or restrict anything. Masking is driven entirely by field masking rules, which are configured separately and do not read this flag.

Use them to record intent, and treat them as documentation for your own team. Do not rely on either as a control. If you need an attribute hidden, write a masking rule; if you need it out of search, it currently cannot be excluded.

Composite attributes

A composite holds named sub-fields rather than one scalar. Matching can target a sub-field with dotted notation.

Each sub-field accepts:

FieldAcceptsDefaultWhat it does
name1–100 chars, camelCase, must start with a letterThe identifier used in dotted paths, as in address.postalCode.
displayName1–255 charsThe label shown for this part.
dataTypeOne of the ten scalar typesThe part's shape.
isRequiredtrue or falsefalseWhether this part must be present.
maxLength1–10,000Length ceiling for text parts.
formatUp to 100 charsA format hint for this part.
rdmLookupTypeIdA lookup typeConstrains this part to a controlled list — a country part bound to ISO 3166, for example.

A composite may hold between 1 and 40 sub-fields.

Composites do not nest

A sub-field's type is drawn from the ten scalar types only — json and composite are excluded. So an address may contain a postal code, but it cannot contain another composite. One level, deliberately: nested composites have no sensible dotted path and no comparator that could score them.

RuleBehaviour
A sub-field that does not existRejected when the profile is saved
A scalar comparator on a whole compositeRejected — two different composites would compare as equal
A composite-aware comparator on a whole compositeAllowed
A non-object value written to a composite attributeRejected, at both the API and the database

The rejection of scalar comparators on whole composites is not a convenience check. Without it, two entirely unrelated addresses compare as equal and the entity type over-merges catastrophically.

Multi-value attributes

BehaviourDetail
StorageSeveral values per attribute, each optionally carrying a usage type
PreferredAt most one preferred value per usage type on the golden record
MatchingValues are enumerated; the best-scoring pairing is taken
BlockingEach value emits its own key
SurvivorshipGrouped by usage type; each resolves independently

Relationship types

SettingPurpose
Source and target entity typesWhat the relationship links
CardinalityHow many links are permitted per endpoint
Display name and inverse display nameSo the link reads correctly from either end
SymmetricWhether direction is meaningful
AcyclicWhether cycles are rejected

Acyclic matters for hierarchies: without it, an ownership chain can close into a loop that no rollup can resolve.

Validation on the model

Referenced configuration must exist and be active when you save. A profile naming an attribute that does not exist, or a rule naming a retired lookup type, is rejected at that moment rather than failing silently later.

This is the general contract: configuration that could not work is refused while you are looking at it.

Next


Last verified against commit a0765982 (2026-08-03)