Writing Rendering Documents

Advanced

The full vocabulary: values from the current element, blocks, related-element queries, tables and shortening filters.

A rendering document is Markdown with two additions: interpolation for values from the current element, and blocks for content Metapad assembles for you.

Throughout, the element a page is about is called the subject.

Values from the subject

Use {{ ... }} anywhere in your text:

  • {{ subject.label }} — the element's name
  • {{ subject.description }} — its description
  • {{ subject.properties.owner }} — any property, by name
  • {{ subject.auto_id }} — its short id, such as SRV-12

A reference that cannot be resolved renders as nothing rather than an error, so check your spelling in the preview.

Blocks

A block is a line starting with :::

BlockWhat it does
:::prose{ source=subject.description }Renders a text property as Markdown
:::propertiesThe subject's properties as a table
:::view{ source=... }A list or table of related elements
:::metric{ source=... label="..." }A single number
:::diagram{ source=... }A diagram, drawn into the page

Pulling in related elements

:::view needs to know which elements. You define that once at the top of the document in a query_spec block, then refer to it by name.

To show, on every Process page, the Roles involved in that process:

{"sections":[{"name":"roles","selection":{"Relative":{"hops":[{"rel":{"relationship_type_ids":["involves"],"direction":"Outgoing"}}]}},"shape":{"Collect":{"include":{"node_properties":"All","edge_properties":"All"}}}}],"locale":"en"}

and then, anywhere below:

:::view{ source=roles }

Use the exact relationship type name from your model — a name that does not exist matches nothing, silently. direction is Outgoing, Incoming or Both; incoming is how a page shows "what points at me".

Two ways to present a view

A table, with the columns you choose:

:::view{ source=roles columns="label, description | summary: 160, properties.owner" }

A list, or any other shape, with a per-item template:

:::view{ source=roles template="- [{{ label }}](node:{{ id }}) — {{ via.involvement }}" }

Inside a template, {{ label }}, {{ id }}, {{ description }} and {{ properties.x }} refer to each item, not to the subject. {{ via.x }} reads a property of the relationship that connects them — which is where an involvement description or a RACI letter usually lives.

A template renders each item on its own, so a template shaped like a table row will not assemble into a table. That is what columns= is for.

A column is headed by the last part of its expression — properties.owner becomes owner. Write as to choose the heading yourself, which also makes it translatable:

:::view{ source=roles columns="auto_id as ID, label as Name" }

Keep long text short

A full description in a table cell wrecks the layout. Pipe it through a filter:

  • | summary: 160 — the lead paragraph, flattened and cut to 160 characters (reach for this one)
  • | first_paragraph — just the lead paragraph
  • | truncate: 200 — cut to length

Linking between pages

Writing [{{ label }}](node:{{ id }}) links to another element's page. In the published site it becomes a real link; if that element is not part of the publication it degrades to plain text rather than a dead link.

ID columns link themselves. In a columns= table you do not need to build the link at all — an auto_id column becomes a link to that element's page:

:::view{ source=roles columns="auto_id, label" }

The same rule applies: if that element is not part of the publication, the ID stays plain text rather than becoming a dead link. Only ID columns do this — to make the name clickable, use a template with [{{ label }}](node:{{ id }}).

Let a block carry its own heading

Your prose is printed exactly as written — including headings — even when the block below it finds nothing. A hand-written ## Roles involved shows up on every page, because only the block knows whether it found anything.

So put the heading on the block:

:::view{ source=roles heading="Roles involved" level=2 empty="No roles assigned yet." }
  • heading= prints only when the block has something in it. level= sets the heading level (2 if you leave it out).
  • empty= is what a reader sees instead when it has nothing. Leave empty= out and the block — heading included — disappears completely.

The same works for a description that might be missing:

:::prose{ source=subject.description heading="Background" empty="No description yet." }

For :::prose, "nothing to show" means the property is empty or not set.

heading= and empty= are ordinary text, so they get translated along with the rest of the page.

Check both formats

Heading levels differ between the website and the PDF: a # heading is the page title on the site, but nests under the chapter heading in the PDF. If a document matters in both, preview it in both.

This content was written collaboratively with AI.