Meridian handbook
Content modelling

Relations

The relation field: references from a document to other documents, tags, smart lists, assets, organisations or people, with targets, cardinality, tag filters, publish checks, stored shape, and how templates receive resolved entries.

A relation field points at other records by id: a walk’s nearby places, an article’s related articles, a listing’s category tag, a page’s featured list. A relation to a document becomes that document’s entry in templates, in the visitor’s locale, and a target that is not live is left out, so a page never links to nothing.

Use a relation when the template needs the target’s fields (its title, image, address). Use a link when the editor is making a link with its own text.

fields:
  - name: nearbyListings
    type: relation
    to: [listing]
    multiple: true
    max: 12
    index: true

  - { name: relatedArticles, type: relation, to: [article, page], multiple: true, max: 3 }

  - name: theme
    type: relation
    to: tag
    filter: { kind: topic, depth: 1 }

  - { name: featuredList, type: relation, to: smart_list }

These four fields, on a walk type, give this model. Each line is one relation field, read from the type that declares it; a multiple field points at many targets, a single one at one or none:

erDiagram
  walk }o--o{ listing : "nearbyListings (max 12)"
  walk }o--o{ article : "relatedArticles (max 3)"
  walk }o--o{ page : "relatedArticles (max 3)"
  walk }o--o| tag : "theme (topic, depth 1)"
  walk }o--o| smart_list : "featuredList"
  listing }o--|| tag : "primaryTag (core, depth 2)"

Relations point one way: a listing does not gain a field when a walk names it. To find the walks that name a listing, query by the relation (below).

Parameters

ParameterTypeDefaultWhat it does
toa key or a list of keysrequiredWhat may be chosen: document type keys (listing, article, a custom type), tag, smart_list, asset, and in engine-dmo organisation and person.
multiplebooleanfalseAn ordered list of targets.
minwhole numbernoneThe fewest targets, when the field is not empty. Use required for “at least one”.
maxwhole numbernoneThe most targets.
filtermappingnoneFor to: tag: { kind, depth } limits the picker to tags of a kind and an exact depth in the vocabulary (depth 1 is a top-level tag). Other targets accept filter but do not apply it.

engine-dmo check does not check that the keys in to name real types: a misspelt type key gives a field whose picker offers nothing. Check the field in the admin, or with engine-dmo describe type <key>.

TargetThe picker offers
Document typesDocuments of those types on the site, in the editing locale, through the reference picker.
tagThe tag vocabulary as a tree, narrowed by filter.
smart_listThe public document lists of the site.
asset, organisation, personNo picker: a plain input that takes the record’s id as type:id. Prefer an asset field for media.

Validation

CodeMessage
multipleChoose one
max_choicesChoose 12 or fewer
min_choicesChoose at least 2
relation_typeMust be one of listing

At publish:

CodeSeverityWhen
tagerrorA tag relation names a candidate tag or an unknown key: a candidate must be promoted into the vocabulary before it is published.
relation_unpublishedwarningA target is not published in this locale or its fallbacks; visitors will not see it.
relation_fallbackwarningA target is not published in this locale but is in a fallback locale, which visitors see.
relation_missingwarningA target is in the trash or no longer exists.

Stored value

{ "id": "5b0c9d7e-...", "type": "listing" }

With multiple, a list of these in the editor’s order. type is the target’s type key, or tag, smart_list, asset, organisation or person. A tag and a smart list are named by their key ({ "id": "eat-drink.brewery", "type": "tag" }). When to names a single target, type may be left out of an imported or seeded value.

In a seed file, a document is named by its seed key and type, { id: hook-norton-brewery-tours, type: listing } (Seeding and importing).

Every relation to a document is recorded as a reference, whether or not the field is indexed, which is how the engine knows what points at a document. With index: true, the targets are also projected (documents by id, tags and smart lists by key), so lists and query() can filter by them.

Template value

A relation to a document becomes the target’s entry: its fields, title, uri, url and the rest, as the target’s own page would see it. uri is the target’s path on this site with the locale prefix, and url the same address made absolute; link with uri|url, and keep url for addresses that must be absolute (a feed, an email). Both are empty when the target has no route on the site (Links and URLs).

  • The entry is the target in the request locale, else in the first locale of the fallback chain where it is published on the site.
  • A target that is not live anywhere in the chain, is trashed, or is not on the site is dropped. A single relation becomes nothing; a multiple relation becomes a shorter list.
  • The target’s own relations resolve when a template reads them, so entry.nearbyListings[0].primaryTag works without loading every relation of every target up front.
  • Targets that are not documents (tag, smart_list, asset, organisation, person) arrive as references with id and type.
{% if entry.nearbyListings %}
<h2>Nearby</h2>
<ul>
  {% for place in entry.nearbyListings %}
  <li><a href="{{ place.uri|url }}">{{ place.title }}</a>, {{ place.summary }}</li>
  {% endfor %}
</ul>
{% endif %}

To list the documents that point at this one, filter by the relation with query(), which needs index: true on the field:

{% for w in query('walk').where('nearbyListings', entry.id).limit(5).all() %}
  <a href="{{ w.uri|url }}">{{ w.title }}</a>
{% endfor %}

See Globals for query() and nearby().

Relations the engine adds

  • Guides. The core guide type’s items is a repeater whose document relation may point at any type that has a route on some site.
  • Collections. The core collection type’s smartList is a relation to a public smart list.
  • Nesting. A type routed with nesting carries a parent relation to its own type (Sites, locales and routes); templates read entry.ancestors and entry.children.
  • engine-dmo. A listing’s primaryTag is a relation to a topic tag at depth 2 (filter: { kind: topic, depth: 2 }), and listings belong to an organisation through the CRM, not a relation field (Partners and the portal).