Relations
Relations & References
A relation property links a record to one or more other records. It is the
single, unified way to model references in the platform — replacing the older
foreignId field and the free-floating __links bag with one primitive.
Relation properties are backed entirely by the relations collection. The
reference value is never stored inline on the record; it lives as an edge in
relations, which means reverse lookup ("what points at me?") is a single
indexed query from either side — no scanning of other collections.
Declaring a relation property
A relation property is a normal schema property whose x-type-schema uses the
relation input/view/filter widgets. Its behaviour is controlled by a small
configuration:
| Option | Effect |
|---|---|
allowedTypeIds unset |
Any record of any type may be linked (the old "links bag" behaviour). |
allowedTypeIds: ["companies"] |
Single-type reference (the old foreignId behaviour). |
allowedTypeIds: ["contacts", "companies"] |
Polymorphic — any of the listed types. |
type: "array" (minItems/maxItems) |
Many cardinality — a list of references. A non-array property is one (single reference). |
inverse: { type, property } |
Opt-in bidirectional pairing (see below). |
descriptions: { [typeKey]: InlineTranslation[] } |
Preset link descriptions offered when creating an edge (see below). |
Cardinality lives on the property, never in the relations store — the
store stays a simple edge list. allowedTypeIds holds bare type ids, not full
tenant-<projectId>-<typeId> collection names.
The free-floating links property
Every record type can opt into a built-in links relation property — a
many-cardinality bag with no allowedTypeIds by default, so any record can be
linked. It is configured from the type settings page (Verlinkungen section:
enable, allowed types, and description presets) which writes the property into
the type's schemaOverrides. Templates and feature extensions declare it
statically via buildLinksRelationProperty. This replaces the legacy injected
__links bag; the edges it produces are tagged property: "links".
Per-link descriptions
A relation property may define descriptions, a map keyed by target type (full
tenant collection name or bare type id) to a list of InlineTranslation
presets. When the configured target type is chosen in the input widget, a
description <select> appears so the user can label the link (e.g. Owner,
Real Estate Agent). The chosen string is stored on the edge's Link.description
and rendered (italic) by the view widget.
How it is stored
Each link in a relations edge carries a property tag identifying which named
property on the source record it satisfies:
{ links: [
{ collectionName: "contacts", id: "ct1", property: "company" },
{ collectionName: "companies", id: "acme" }
] }
The tag is what lets two properties that target the same type — e.g. company
and referredBy both pointing at companies — stay distinct. View and filter
widgets fetch all edges for the record and filter by property at render time.
Display labels are always resolved live from the target's
representationText; nothing is cached on the edge, so renaming a target record
updates every reference automatically.
Viewing, editing and filtering
The relation property ships the full widget trio:
- View — renders the linked records (property-scoped), resolving each label live.
- Input — pick target records;
allowedTypeIdsrestricts the selectable types, and single-cardinality replaces rather than appends. - Filter — filter a list by "records linked to this target via this property", resolved through the
relationsstore.
Bidirectional relations (opt-in)
By default a relation is one-directional: if a Company declares an employees
property pointing at Employees, the company shows its employees, and the
employee side shows nothing.
To make it two-way, the schema author declares an inverse once:
Company.employees → inverse: { type: "<employees type>", property: "employer" }
Now linking a company to an employee also tags the employee-side edge as
employer, so the employee's employer property shows the company. This is a
design-time decision — end users never pick the reverse property per link.
If only one side declares the relation, the other side simply shows nothing.
Relationship to foreignId and __links
The unified relation property supersedes both older mechanisms:
foreignId— single-type inline references are replaced by a relation property with oneallowedTypeIdsentry.__links— the free-floating bag is fully replaced by thelinksrelation property (noallowedTypeIds). The runtime__linksinjection and the legacylinksConfigurationtype field have been removed.
The __links → links cutover is complete: migration 027 converts any stored
linksConfiguration into a links relation property in the type's
schemaOverrides and removes the legacy field. The edges keep the
property: "links" tag, so no edge data is reshaped.