Writing Rendering Documents
AdvancedThe 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 asSRV-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 :::
| Block | What it does |
|---|---|
:::prose{ source=subject.description } | Renders a text property as Markdown |
:::properties | The 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. Leaveempty=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.