Skip to content

Block language reference

Ferrith ChatFor authorsFor integratorsVerified 2 Oct 2026

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:

text
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 keyed logo is written {{ logo }}, one keyed firm.logo is 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.00
  • words — on whole number, number or money — Writes an amount in words.
    {{ gift.amount | words }} → one thousand pounds
  • ordinal — on whole number — Writes a whole number as an ordinal.
    {{ clause.number | ordinal }} → 3rd
  • format_date — on date — Formats a date with the given pattern.
    {{ will.date | format_date "d MMMM yyyy" }} → 3 March 2026
  • names — on people — The full names of a list of people, as a list.
    {{ executors | names | list }} → Ann Smith and Bob Jones
  • list — 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 assets
  • string.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:

text
{{ 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; !field is 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): the else part 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 if needs its end; the else part is optional.

Repeats

Repeat text for each item of a list field — a list of people, or several picked from a list:

text
{{ 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 has full_name and address, a picked option is the text itself.
  • Inside the repeat, for.first, for.last and for.index say where you are.
  • A list has size, first and last: {{ 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:

text
::: 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:

text
{{ 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:

text
::: 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:

text
{{ 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:

  • include isn't available in building blocks — a block is a complete piece of text on its own.
  • include_join isn't available in building blocks — a block is a complete piece of text on its own.
  • import isn't available in building blocks.
  • tablerow isn't available in building blocks — it writes HTML, and blocks are Markdown.
  • object.eval isn't available in building blocks.
  • object.eval_template isn't available in building blocks.
  • date.now isn't available; use document.assembled_on, so the same answers always produce the same document.
  • date.utc_now isn't available; use document.assembled_on, so the same answers always produce the same document.
  • html functions 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.