Block language reference
The language a building block's text is written in: Markdown for the words, with merge fields, conditions and repeats between double braces. The editor checks the text as you type — an error blocks publishing, never saving.
A line break is a line break: text you put on the next line prints on the next line — an address, a signature block — and a blank line starts a new paragraph. Markdown's own line endings (two spaces, or a backslash, at the end of a line) work too, but you need neither.
Merge fields
A field's answer goes wherever its key appears between double braces:
I, {{ testator.full_name }} of {{ testator.address }}, revoke every earlier will.
- Keys are lower-case words joined by dots and underscores —
testator.full_name. The dots only group related fields under a prefix: a field keyedlogois written{{ logo }}, one keyedfirm.logois written{{ firm.logo }}. The Fields tab of the library lists every key a block may use, and Insert a field writes them for you. - A field with no answer prints its label in brackets —
[Testator full name]— so a gap is visible in the draft. Mark a field optional on the block and guard it with a condition when the text should read well without it. - A block tagged to document types may use only the fields available to all of them; the check names any field that isn't, and the editor offers only the fields the block can use.
- A field's kind decides what you can do with it: text, long text, a date, a whole number, a number, money, yes / no, one of a list, several from a list, a person, people, an address.
Formatting
A function after a | formats the answer:
money— on whole number, number or money — Formats an amount with two decimals and a currency sign.
{{ gift.amount | money }}→ £1,000.00words— on whole number, number or money — Writes an amount in words.
{{ gift.amount | words }}→ one thousand poundsordinal— on whole number — Writes a whole number as an ordinal.
{{ clause.number | ordinal }}→ 3rdformat_date— on date — Formats a date with the given pattern.
{{ will.date | format_date "d MMMM yyyy" }}→ 3 March 2026names— on people — The full names of a list of people, as a list.
{{ executors | names | list }}→ Ann Smith and Bob Joneslist— on several from a list or people — Joins a list with commas and a final "and".
{{ will.special_provisions | list }}→ funeral wishes, pets and digital assetsstring.upcase— on text, long text, address or one of a list — Upper-cases text.
{{ testator.full_name | string.upcase }}string.downcase— on text, long text, address or one of a list — Lower-cases text.
{{ testator.full_name | string.downcase }}string.capitalize— on text, long text, address or one of a list — Capitalises the first letter.
{{ gift.description | string.capitalize }}array.join— on several from a list or people — Joins a list with the given separator.
{{ will.special_provisions | array.join ", " }}string.contains— on text, long text, address or one of a list — Whether text contains the given words — for conditions.
{{ if testator.address | string.contains "London" }}…{{ end }}image— on image — Prints an image at a width in millimetres; without it, at the field's own width.
{{ firm.logo | image 30 }}ref— on text — The number of another block by its key — or of a numbered item in it, naming the item's anchor as well — and a schedule's or part's label by its section key.
subject to clause {{ ref "executors" }}anchor— on text — Names a numbered item in this block so another block can refer to it with ref.
1. {{ anchor "conditions" }}The Tenant must…page_break— Starts a new page in the PDF and Word exports; write it on a line of its own.
{{ page_break }}
The library's functions — money, words, ordinal, format_date, names, list, ref,
anchor and page_break — are written bare; the text functions carry their object name, as in
{{ testator.full_name | string.upcase }}. Functions chain left to right:
{{ executors | names | list }} takes the people's names, then joins them.
An image field holds a picture — a logo, a seal — uploaded on the field or by the
person drafting, never typed. {{ firm.logo }} prints it at the width set on the field and
{{ firm.logo | image 30 }} at 30 mm. Put the tag on a line of its own, inside an alignment
fence to centre it or set it to the right. A picture cannot be written into a block
directly.
Conditions
Include text only when a condition holds:
{{ if children.minor_count > 0 }}
I appoint {{ guardians | names | list }} as guardians of my children.
{{ else }}
I have no children under eighteen.
{{ end }}
- A field on its own is true when it has an answer and false when it is empty;
!fieldis the reverse. - Compare with
==,!=,>,<,>=and<=. Numbers are written without quotes, text in double quotes:funeral.wishes == "Cremation". - If a field you compare has no answer, the comparison is false (
!=is true): theelsepart prints, and the draft's To finish card lists the field as not answered. Where an empty answer is expected, test the field first and it is not listed:{{ if gift.amount && gift.amount > 1000 }}. {{ if testator.address | string.contains "London" }}tests text for words.- A list has
size:{{ if executors.size > 1 }}Executors{{ else }}sole Executor{{ end }}. - Join tests with
&&(all must hold) or||(any may hold). - Every
ifneeds itsend; theelsepart is optional.
Repeats
Repeat text for each item of a list field — a list of people, or several picked from a list:
{{ for executor in executors }}
{{ executor.full_name }} of {{ executor.address }}{{ if for.last }}.{{ else }}, and{{ end }}
{{ end }}
- Name each item as you like after
for; a person hasfull_nameandaddress, a picked option is the text itself. - Inside the repeat,
for.first,for.lastandfor.indexsay where you are. - A list has
size,firstandlast:{{ executors.size }}counts them.
Headings, alignment and numbering
A block's text is Markdown, so it may carry its own headings — # Last Will and Testament,
## Executors — and **bold** and *italic* for emphasis. The document type's sections
supply headings too; switch a section's heading off on the type when its blocks supply
their own.
Align lines by putting them between alignment fences:
::: centre
# Last Will and Testament of {{ testator.full_name }}
:::
::: right
{{ sender.address }}
{{ document.assembled_on | format_date "d MMMM yyyy" }}
:::
The words are left, centre, right and justify; the closing line is ::: on its
own. Alignment applies in the preview and in the DOCX and PDF exports.
A numbered block gets its clause number on its first line of prose — a heading or an
alignment fence at the top keeps its own line. Switch Number this block as a clause off in
the block's details for text that should appear as it is: a heading, a recital, an address.
An unnumbered block has no clause number for ref to print.
A document type may number the lists inside its blocks as sub-paragraphs: set its
sub-paragraph numbering to legal and a numbered list prints as 4.7.1, 4.7.2 under clause
4.7, a list inside an item as (a), (b), one inside that as (i), (ii), each level
indented — the shape a lease or a contract takes. As written, a list prints as you typed it.
A section of the type may be a schedule — labelled "Schedule 1", "Schedule 2" in the order they print, its paragraphs numbered from 1 — and may hold parts, labelled "Part 1", "Part 2" under it, each numbered from 1. A schedule or part whose blocks are all left out of a document prints nothing, heading included, and the ones after it move up.
A table in a block — a pipe table, a row per line with | between the cells and a
|---|---| line under the headings — prints as a table in the preview and in both exports.
Pages
The PDF and Word exports print on A4 pages. Start a new page with {{ page_break }} on a
line of its own — inside a condition too, so the break comes and goes with the text around it:
{{ if lease.front_sheet }}{{ page_break }}{{ end }}
Put text on a page of its own between page fences — ::: top, ::: middle or
::: bottom says where it sits on the page — with alignment fences inside as usual:
::: middle
::: centre
# Commercial Lease
:::
of {{ premises.address }}
:::
A page of its own starts and ends its own page, so it needs no page break around it, and text too long for one page carries on to the next. A section of the document type can start on a new page too: switch New page on for it — for each schedule, say. Page breaks don't add up: a break at the very start or end of a document, a second break in a row and a break next to a page of its own add nothing, so a blank page needs something on it. In the preview a page break shows as a dashed rule and a page of its own as a dashed box.
Cross-references
{{ ref "executors" }} prints the number of another block by its key — 3, or 4.7 where the
type numbers within each section — worked out when the document is assembled, so a reference
stays right when clauses move. Write the word yourself: clause {{ ref "executors" }},
paragraph {{ ref "rent-review" }} of {{ ref "schedule-rent" }}.
{{ ref "section-key" }}names a section: a schedule or a part prints its label —Schedule 2,Part 1— and a clause section its number where the type numbers within each section.{{ anchor "conditions" }}at the start of a numbered item names it, and{{ ref "break-clause" "conditions" }}in another block prints that item's full number —8.1.2,8.1.2(a). Anchors work when the type's sub-paragraph numbering is legal; with numbering as written they are removed and a reference to one reads as missing.
The check warns when the block it points at has no published revision yet, and refuses a key that is neither a block nor a section of the document types the block is placed in.
The document itself
{{ document.assembled_on | format_date "d MMMM yyyy" }}— the date the document is produced.{{ document.title }}— the document's title.
There is no clock in a block: the same answers always produce the same document.
Headers and footers
A document type may carry a header and a footer, printed on every page of the
exported document. They are written in the same language, with two values that exist only
there — {{ page.number }} and {{ page.count }}, the page number and the number of pages:
{{ firm.name }} — page {{ page.number }} of {{ page.count }}
A header or footer merges fields and uses the formatting functions like a block, but not
ref (there is no clause to point at from the edge of a page) or a page break, and a block
cannot use page (a block does not know which page it lands on). The header can be left off the first
page.
Not available
Some constructs are refused by name, with the reason:
includeisn't available in building blocks — a block is a complete piece of text on its own.include_joinisn't available in building blocks — a block is a complete piece of text on its own.importisn't available in building blocks.tablerowisn't available in building blocks — it writes HTML, and blocks are Markdown.object.evalisn't available in building blocks.object.eval_templateisn't available in building blocks.date.nowisn't available; usedocument.assembled_on, so the same answers always produce the same document.date.utc_nowisn't available; usedocument.assembled_on, so the same answers always produce the same document.htmlfunctions aren't available — blocks are written in Markdown.
Raw HTML tags are refused too — a block is Markdown, so **bold** and *italic* are the
way to emphasise, and the alignment fences above the way to lay lines out. A Markdown image
() is refused as well: a picture reaches a document through an image field.
The whole language
The editor accepts the whole language: variables ({{ share = 100 / executors.size }}),
case / when, while, capture, functions, and the builtin objects string, array,
math, date, object, regex and timespan.
Some limits keep a block honest: a loop runs at most 1,000 times, output stops at 256 KB, a pattern has one second to match, and the whole block has two seconds to produce its text.