Who this is for. Content modellers turning a flat list of records into a domain model, and developers rendering it. After this page you can choose a relationship's limits, predict when a save is refused, and read related records in either direction without loading more than you need.
What a relationship definition is
Records on their own are a flat list. A relationship definition says that records of one definition may point at records of another: an enrolment points at a course, a course at the courses it requires first. It belongs to the definition on the pointing side, the owning side, and has these fields:
| Field | What it controls |
|---|---|
| Identifier | The name templates and the API use for the relationship, such as course. |
| Name | What authors see. |
| Position | The order of the relationships on a record. |
| Minimum records | How many related records a save requires. 0 makes the relationship optional. |
| Maximum records | How many it allows. Empty means no limit; 1 makes it a single relationship. |
| Allowed entities | The definitions whose records may be linked. At least one is required. |
Three properties shape everything else:
- Targets are always custom entity records. Allowed entities lists definitions, never pages, users or products. To point a record at a page, a member or a product, give its layout a record or records layout area instead.
- A relationship belongs to the record, not to a revision. Changing it changes the live record at once; there is no revision to activate.
- A relationship has a direction. The owning side holds the link and reads it directly. The other side finds it only by asking which records point at it, which is a query (reading from the other side).
Creating one
Relationship definitions are added on the owning definition in the admin. The learning site in this unit's worked example has two:
| Owning definition | Identifier | Minimum | Maximum | Allowed entities |
|---|---|---|---|---|
enrolment | course | 1 | 1 | course |
course | prerequisites | 0 | empty | course |
A relationship may target its own definition, as prerequisites does. Choose the limits from what the model means, because templates see them too:
| Minimum and maximum | Means | The Smarty shorthand returns |
|---|---|---|
| 0 and 1 | Optional, one record | The record, or null |
| 1 and 1 | Required, one record | The record |
| 0 and empty | An optional list | A collection, possibly empty |
| n and m | Between n and m records | A collection |
Changing the maximum to or from 1 later changes what the shorthand returns, from a record to a collection or back, so every template that reads the relationship has to change with it.
What a save checks
- The minimum, twice. A save is refused when a relationship has fewer records than its minimum, and also when the record has no relationship at all for a relationship definition whose minimum is above 0. An enrolment that never had a course set is refused, not only one whose course was removed.
- Each linked record's definition. A linked record whose definition is not among the allowed entities is refused, record by record.
- The maximum. A save with more linked records than the maximum is refused, reported against
recordfor a single relationship and againstrecordsfor a list.
The minimum's message names the relationship: A minimum of 1 need to provided for the relationship course, but got no values when none is linked, and A minimum of <n> need to provided for the relationship <identifier>; only got <count> when some are linked but too few. It is reported against record for a single relationship and against records for a list. Over the API, a refused relationship write is a 422 that names the relationship (Custom entities over the API and MCP).
Reading from the owning side
In Smarty, a relationship's identifier works as a property of the record, like the layout area shorthand: {$enrolment->course} returns the course record when the maximum is 1, and a collection of records otherwise. In Twig, enrolment.course prints nothing, so a Twig template reads a relationship with the explicit getters.