Skip to main content

Relations

Relations connect objects to other objects. A relation is defined by the class it points at: when an object is linked through a relation, it receives that class as an additional class — along with all the fields and behaviors that class defines.

Definition​

{
"class": "commons.employee",
"required": true,
"multiple": false
}
PropertyTypeDefaultDescription
classQualifiedName—The class the relation points at. It identifies the relation, and it is added to every linked object.
requiredbooleanfalseIf true, the relation must be filled in.
multiplebooleanfalseIf true, multiple objects can be linked.
cascadeDeletebooleanfalseIf true, deleting this object also deletes all linked objects.
commonRelationQualifiedName—Limits selectable objects to those already linked to the same object of this class as the current object.
inheritQualifiedName—Enables hierarchical filtering by walking a parent chain on the target class. Points to the class of that parent relation (an abstract role class).
inheritTwoWaybooleanfalseWhen true, hierarchical filtering traverses the chain in both directions.
defaultValuestring—Pre-filled value when a new object is created. See Default value tokens.

A relation is identified by its class​

A class has at most one relation to a given class, and that class is also how the relation is named: in the API, a relation to commons.company is company (or companies for a multiple relation). There are no separately named relations.

This is deliberate. When a class needs two relations to the same kind of object — a sender and a receiver of an email, a buyer and a seller in a transaction — declare an ABSTRACT role class for each, and relate to those:

{
"version": 1,
"class": "my-project.emailSender",
"kind": "ABSTRACT",
"inherits": "commons.entity"
}
"relations": [
{ "class": "my-project.emailSender", "required": true, "multiple": false },
{ "class": "my-project.emailReceiver", "multiple": true }
]

The role is then something the linked object becomes: a person linked as the sender gains the class my-project.emailSender, can carry fields of that role, and can be found and filtered as a sender. See How relations add classes.

Default value tokens​

defaultValue pre-fills the relation when a new object is created through an auto-generated form. It applies to every layout that shows the relation — a layout item's own RELATION.defaultValue overrides it for that specific layout.

The only token currently supported is me(), which the client resolves to the currently logged-in user, fetched from /accounts/{tenantId}/me — the person the caller's profile links to:

{ "class": "commons.author", "required": false, "multiple": false, "defaultValue": "me()" }

commons.memo uses this on its author relation, so a new memo is pre-filled with its creator without anyone having to pick themselves from a list. See Memo archetype.

How relations add classes​

When an object is linked through a relation, the linked object receives the relation's class as an additional class. This adds all fields and formulas of that class to the linked object — without changing the object's BASIC class.

Example:

{
"version": 1,
"class": "commons.employmentAgreement",
"kind": "BASIC",
"inherits": "commons.agreement",
"relations": [
{ "class": "commons.employee", "required": true, "multiple": false },
{ "class": "commons.company", "required": true, "multiple": false }
]
}
{
"version": 1,
"class": "commons.employee",
"kind": "ABSTRACT",
"inherits": "commons.person"
}

When a commons.person is linked as the employee on an employmentAgreement:

  • The person retains its BASIC class: commons.person
  • It gains the class commons.employee
  • It can now store fields defined on commons.employee (e.g., employee number, start date)

Searching for linkable objects​

When linking a relation, the platform needs to know which objects are eligible. It does this by walking up the inheritance chain of the relation's class and finding the most specific class that is inherited by at least one BASIC class.

Example with an ABSTRACT relation class:

{
"version": 1,
"class": "my-project.shareholder",
"kind": "ABSTRACT",
"inherits": "commons.entity"
}

The relation points at my-project.shareholder. Walking up:

  • my-project.shareholder (ABSTRACT, no BASIC class inherits this directly)
  • commons.entity (ABSTRACT, inherited by commons.person and commons.company)

The platform searches for objects of class commons.entity — meaning both persons and companies appear in the search results.

Creating new objects through a relation​

If no existing object fits, the user can create a new one directly from the relation. The platform looks for all BASIC classes that inherit the searchable class (commons.entity in the example above) and presents them as options: commons.person or commons.company.

The newly created object automatically receives the relation's class (my-project.shareholder) as an additional class.

Cascade delete​

When cascadeDelete is true, deleting an object also deletes all objects linked through that relation.

{
"class": "my-project.invoiceLine",
"multiple": true,
"cascadeDelete": true
}

Use this for objects that have no meaningful existence without their parent — for example, invoice lines that belong to a single invoice.

Hierarchical filtering​

inherit enables filtering through a parent chain on the target class. It points to the class of the parent relation that forms that chain.

Building the hierarchy​

A class may never declare a relation whose class is its own qualified name. The platform rejects a direct self-reference at model load with an error. Model the parent link through a dedicated ABSTRACT role class instead — one that inherits the class it organises and is used for nothing else.

For a territory tree, that role class is superTerritory:

{
"version": 1,
"class": "my-project.superTerritory",
"kind": "ABSTRACT",
"inherits": "commons.territory"
}

The parent relation is then added to commons.territory itself — here through an EXTENSION, because commons.territory is a commons class:

{
"version": 1,
"class": "my-project.territoryHierarchy",
"kind": "EXTENSION",
"extends": "commons.territory",
"imports": ["my-project.superTerritory"],
"relations": [
{ "class": "my-project.superTerritory", "multiple": true }
]
}

Since my-project.superTerritory inherits commons.territory, any territory can fill the slot, and linking one grants it the my-project.superTerritory class — see How relations add classes. A country is still created as a commons.country; hanging a region under it is what makes it a super territory. The result is the same tree, without a direct self-reference:

Amsterdam → North Holland → Netherlands → Europe → World

Filtering through the chain​

A commons.company has a relation to commons.territory. Setting inherit to the role class on that relation tells the platform to discover the parent chain and walk it when filtering:

{
"class": "commons.territory",
"inherit": "my-project.superTerritory"
}

A company linked to Amsterdam can now be found by filtering on North Holland, Netherlands, or any ancestor — even though it is only directly linked to Amsterdam.

Discovery rule: the platform looks for a relation on the target class (commons.territory) whose own class equals the inherit value (my-project.superTerritory). That relation defines the chain to walk.

One-way vs two-way​

inheritTwoWay: false (default) — only ancestors of the stored value match. Filtering on a general territory finds all companies in any sub-territory:

Company linked toFilter on NetherlandsFilter on Amsterdam
Amsterdam✓ (Amsterdam is under Netherlands)✓
North Holland✓ (North Holland is under Netherlands)—
Netherlands✓—

inheritTwoWay: true — descendants also match. A company linked to Netherlands is also found when filtering on Amsterdam:

Company linked toFilter on NetherlandsFilter on Amsterdam
Amsterdam✓✓
Netherlands✓✓ (Netherlands is above Amsterdam)

Use inheritTwoWay when you want filtering to work in both directions, for example in a territory-based access model where users scoped to a sub-territory should still see objects linked to a parent territory.