Merge tag reference
Every merge tag you can use in an uploaded Word template: answers, submission and linked-record details, conditions, repeating rows, photos, and all the modifiers.
This page lists the tags you can put in a Word template for a form or a workpack. How to upload the template and check it is in Upload a Word template.
Word templates use {$field_name}. The in-app editor uses a different style, ${field_name} (see Customize print formatting); the two can't be mixed in one file.
How a broken tag prints
A tag never stops a PDF being made.
- A tag that can't be read, uses a modifier that doesn't exist, has a modifier setting that can't be read or is in curled quotes, is missing its
$, or is an{if}or{foreach}without its close prints exactly as it is written in the Word file. - An
{if}condition that can't be read counts as false: its text is left out, and the{else}text prints instead. - A tag naming something the submission doesn't have prints empty.
- Only a damaged Word file, or the conversion service failing, stops the PDF.
The tag check after each upload tells you which tags will print as written.
Write a tag
A tag starts with { followed straight away by a character ({ site}, with a space, is ordinary text) and ends at the matching }. A tag with no closing } is left as ordinary text.
{$site_location}
{$crew[0].name}
{$textarea-6c5}
- The
$is required.{site_location}prints as written. - Use the field's Name from the form builder, not its label. Automatic names with hyphens, such as
textarea-6c5, work. - Reach inside an answer with dots (
{$worker.name}) or a row number in brackets ({$crew[0].name}). - A table's rows are a list. Each column is read by its Name (see Name a table column). A column without a Name is read by its header in lower case, with underscores for spaces: Used for whole shift becomes
used_for_whole_shift, and a header starting with a digit gets an underscore in front (1st Shift becomes_1st_shift). Two headers that come out the same get_2,_3and so on. - Tags use the form as it is now. A submission made before you renamed a field or column prints under its current Name, so one template prints old and new submissions alike.
- Inside a tag, straight
"and'hold text, so a}inside straight quotes doesn't end the tag (inside curly quotes it does). Word turns quotes curly as you type. Curly quotes work in an{if}condition, but not in a modifier setting:{$id|pad:5:”0”}prints as written. Turn off smart quotes in Word before you type tags. - Keep each tag in one style. A tag with part bold or in another font is stored in pieces and may print in fragments.
Change how a value prints
Add a modifier after a |. Settings follow :, and can be quoted text, numbers, true, false or another $ value. Modifiers run left to right.
{$docket_no|pad:5:"0"}
{$name|trim|upper|truncate:20}
{$amount|currency}
Modifier names are case-sensitive: {$name|Upper} prints as written and the tag check reports UNKNOWN_MODIFIER. All the modifiers are listed under Modifiers.
Submission details
| Tag | Prints |
|---|---|
{$id} | The submission ID |
{$title} | The form's title |
{$submission_id}, {$form_title}, {$submitted_by}, {$version} | The submission ID, the form's title, who submitted it and the version |
{$submission_time}, {$submission_datetime} | The time, or date and time, submitted |
{$date} or {$submission_date} | The date submitted, ready to print |
{$submitted_at} | The date and time submitted, for use with date_format: {$submitted_at|date_format:"%d/%m/%Y"} |
{$project_name}, {$project_number} | The linked project's name and code |
{$client_name}, {$client_number} | The linked client's name and number |
{$workpack_name}, {$workpack_number} | The linked workpack's name and document number |
{$date} and {$submission_date} are already formatted, so date_format returns nothing for them; use {$submitted_at} to format the date your way. Date answers inside tables are kept in a form date_format can read. Don't give a form field any of these names, such as id, date, title or submitted_at.
The project, client and workpack come from the workpack the submission was filled in on, or else from a Project or Client field at the top level of the form. A Project field also fills in its client, so you only need a Client field when the form's client isn't the project's. A Project or Client field that allows several choices uses the first one. A field inside a table doesn't count, because it holds one value per row; reach those values with {foreach}. When nothing is linked, these print -.
Details of a linked record
A Resource field's own tag prints the option the person picked: {$siteProject} gives "Harbour Bridge". For the rest of the record, start with linked, then the field's name, then the detail:
{$linked.siteProject.name}
{$linked.siteProject.number}
{$linked.siteProject.external_id}
{$linked.siteProject.client.name}
- Every record has
display(what the picker showed),name,idandexternal_id. - A single-choice Projects field also has
number(the project code) andclientwith itsnameandnumber. That is the project's own client, which isn't always the form's when the form has a Client field as well. A single-choice Clients field hasnumber. - A multiple-choice field gives a list, so repeat over it:
{foreach $linked.sites as $item}{$item.name}{/foreach}. List items have onlydisplay,name,external_idandid.
Available variables → Linked records lists these for your form. A form field actually named linked keeps its own tag, and the linked details aren't added for it.
Show text only when a condition is met
{if $hours > 0}
Hours: {$hours}
{elseif $hours == 0}
No hours
{else}
Not recorded
{/if}
- Compare with
>,<,>=,<=,==,!=,===and!==, and combine with&&(and) and||(or). - A value counts as empty when it's missing,
false,0, blank or an empty list. Anything else counts as set, including the words "false" and "no". - Checkboxes, switches and ticked table cells arrive as
Yesor empty, neverNo. Test them with{if $field}, not{if $field == "No"}. - Numbers typed as text compare as numbers, so
{if $hours > 0}is true for an answer of "30". - Other text compares exactly, capitals included:
{if $status == "Open"}doesn't matchopen. Put the text in quotes, either straight ones or Word's curly ones ({if $status == “Open”}), but not one of each. - Curly and straight apostrophes inside the text count as the same, so
{if $q == "Don’t know"}matchesDon't knowwhichever apostrophe Word or a phone typed. Put text that contains an apostrophe in double quotes.
A condition Pay can't read counts as false, so its {else} text prints, and the tag check reports BAD_CONDITION: The condition could not be read, so it always counts as false. The usual causes are text without quotes ({if $status == Open}), a single = instead of ==, a field name without $, and a modifier inside the condition ({if $status|lower == "open"}).
What a false condition removes
An {if} can start in one paragraph, table cell or row and end in another, with other text beside its tags. A branch that isn't used takes everything between its tag and the next one, in reading order: the rest of the paragraph or cell after the tag, any paragraphs, rows and tables in between, and the start of the paragraph or cell that holds the next tag.
- Text outside the tags stays where it is.
Label {if $x}Aon one line and{else}B{/if}on the next printsLabel Awhen$xis set, orLabelandBon their own lines when it isn't. - A table cell between the tags is emptied, not removed, so the table keeps its columns. A cell always keeps at least one line.
- That lets one condition fill a tick column: put
{if $q == "Yes"}✓in the YES cell and{else}✓{/if}in the NO cell. With no{else}and nothing printed, the cells between are emptied and the row keeps its question. A false{tableif}still removes its whole row. - A line left with no text is removed, and so is a table row with no text in any cell, and a table with no rows left. Pictures, icons, text boxes, symbols, page breaks, equations and page numbers count as content and stay: a logo beside a company name that a false
{if}removes keeps its place, and a text box whose only text was conditional stays as an empty box. - The same holds in a table inside a cell: a false
{tableif}removes the inner row, and an inner table left with no rows goes, while its cell keeps a line. - A bookmark, comment or field such as a page number that a false branch cuts through keeps working.
Compare a choice answer
A Select, Radio, Multi select or Toggle answer prints as the option's label, and a condition compares that label exactly. Take a Radio field named q1EntrancesSecured with the options Yes, No and N/A. Put one of these in each of the YES, NO and N/A cells of its table row, and the tick lands in the column that matches the answer:
{if $q1EntrancesSecured == "Yes"}✓{else}-{/if}
{if $q1EntrancesSecured == "No"}✓{else}-{/if}
{if $q1EntrancesSecured == "N/A"}✓{else}-{/if}
{if $q1EntrancesSecured == "yes"} never matches, because the answer is the label Yes.
To compare the value stored behind the label instead (the option's Value in the form builder), use values. {$values.q1EntrancesSecured} gives yes, no or na:
{if $values.q1EntrancesSecured == "na"}✓{else}-{/if}
- A Multi select, a Toggle set to multiple choices, or a field with Allow multiple responses on gives a list of stored values, and a list never equals one piece of text. Loop over it and test each value:
{foreach $values.hazards as $value}{if $value == "hot"}✓{/if}{/foreach}. A Multi select's own tag prints the chosen labels separated by commas. - An Other answer gives the Other option's stored value. What the person typed prints with the field's own tag, as
Other: hail. - In a table row, a choice column's stored value is
{$row.values.<column>}, beside{$row.<column>}. - A form field actually named
valueskeeps its own tag, andvaluesisn't added for it.
Available variables → Form fields shows the labels each choice field prints, and its stored-value tag. Preview answers each choice field with its first option, so in the grid above every row previews with its tick under YES.
When a condition compares a choice field with text that isn't one of its labels (or, under values, one of its stored values), the tag check warns you with CHOICE_VALUE_MISMATCH, lists the options and suggests the one to use. It checks against the form as it is now, after upload, when you open the template again, and under the preview. It covers Select, Radio and single-choice Toggle fields, their table columns inside a {foreach} over the table, and sub-table columns inside a loop over the sub-table. Answers that are lists aren't checked.
Repeat for each item in a list
{foreach $crew as $row}
{$row.worker}
{foreachelse}
No crew listed
{/foreach}
{foreach from=$crew item=row} works the same. Add a key with {foreach $crew as $key => $row} or key=key. {foreachelse} prints when the list is empty.
Inside the loop, {$row@index} (counting from 0), {$row@iteration} (from 1), {$row@first} and {$row@last} tell you where you are.
Each column tag uses the column's Name, or its header in lower case with underscores (see Write a tag). Available variables lists a table's column tags, ready to copy.
A table inside a table repeats the same way:
{foreach $worker_time as $row}
{$row.worker}, {$row.role}
{foreach $row.equipment as $eq}
{$eq.asset} ({$eq.hours})
{/foreach}
{/foreach}
Available variables lists a sub-table as a loop inside its table's row, then its columns, and the hint names every loop the tag has to sit inside, for example Inside {foreach $worker_time as $row}{foreach $row.equipment as $item}. The list calls the rows $row, then $item, then $sub; any name works, as long as the tags inside use the same one.
To sort first, add a modifier to the list: {foreach $crew|sort:"worker_name" as $row}. A modifier on a list that doesn't exist makes the loop print nothing, and the tag check reports UNKNOWN_FOREACH_MODIFIER.
Name a table column
A column without a Name takes its tag from its header, so correcting a header changes the tag your template needs. Give the column a Name, and its tag stays the same whatever you do to the header.
- Hover the table and select its settings button. Customize field attributes opens.
- Under Table Columns, select the settings icon beside the column.
- Type the tag in Name. Under the box you see the tag the column prints with, such as
{$row.crew_member}. - Select Save, then Save the form.
A Name is used exactly as you type it, capitals included: CrewMember fills {$row.CrewMember}, not {$row.crewmember}. Use letters, numbers and underscores, and start with a letter or an underscore. Leave Name blank to use the header; the empty box shows that tag.
Pay won't keep a Name that another column in the table already uses, whatever its capitals, and it refuses files and values, because a row uses them for photos and stored values. The reason shows under Name, for example Column "Hours" already uses "hours". Choose a different name. If you select Save anyway, Fix the name of column "Worker" first. appears and the dialog stays open.
In a table with more than one photo column, a column named photo prints its first photo with {$row.files.photo} and every photo with {$row.files.photo_all}. So no other photo column can be named photo_all, and the message reads Photo column "Photo" already prints photos under "files.photo_all". Choose a different name.
If your template stopped printing a column after its header changed, type the old tag into the column's Name: it prints again for old and new submissions.
Repeat a table row
Put {foreach} in the first cell of a Word table row and {/foreach} in its last cell. The whole row is copied once per item. A loop outside a table repeats the text or paragraph it wraps.
- In a table inside a cell, put both tags in the inner table to repeat its rows, or one tag in each table to repeat the outer row.
- A loop inside a row loop, with both its tags in that row, repeats the row once per inner item.
- A line that ends one loop and starts the next (
{/foreach}{foreach $plant as $p}) is split in two, so each loop repeats its own lines. Text between the two tags prints once, on its own line. - A loop written inside one line repeats its text, line breaks, tabs and symbols, but leaves out any picture inside it, and a text box inside it loses its box. To repeat a picture or a text box, put the loop around whole paragraphs or a table row.
Text boxes
Tags work on every line of a text box: values, conditions, loops and images. A {foreach} that opens in the body and closes inside a text box on that line prints as written (UNBALANCED_BLOCK). Put both tags inside the box, or both outside it.
Table shortcuts
{tablerow} works like a table-row {foreach} ({tablerow from=$products item=_p} works too), and {tableif} removes the whole row when its condition isn't met:
{tablerow $products as $_p}
{tableif $_p.Type == 'shirt'}
{$_p.Name} {$_p.Quantity} {$_p.Price}
{/tableif}
{/tablerow}
To drop rows with no value, use {tableif $Product1}. empty() isn't supported: {tableif !empty($Product1)} doesn't work.
Photos, signatures and drawings
Signature and Draw on image answers are pictures. Print them with insert_image and a width and height in pixels:
{$supervisor_signature|insert_image:200:80}
Without insert_image, the tag prints nothing and the tag check reports IMAGE_WITHOUT_INSERT_IMAGE.
Size a picture
A 0 in place of a number means "work it out from the picture".
| You write | What prints |
|---|---|
insert_image:200:80 | Exactly 200 × 80 pixels. Giving both numbers doesn't keep the picture's proportions, so a picture of a different shape is stretched to fit |
insert_image:300:0 | 300 pixels wide, as tall as the picture's proportions make it |
insert_image:0:150 | 150 pixels tall, as wide as the picture's proportions make it |
insert_image or insert_image:0:0 | The picture's own size, shrunk to fit the page's margins if it's bigger. A smaller picture isn't enlarged |
For photos, give a width and a 0 height, such as insert_image:300:0. Photos come in both landscape and portrait, and the 0 keeps each one the right shape. Photos taken on a phone print the right way up.
Photos and files
A photo or file upload tag prints the file name: {$sitePhoto} gives IMG_2841.jpg. For the picture, use files:
| You want | Tag |
|---|---|
| The file name | {$sitePhoto} |
| The photo | {$files.sitePhoto|insert_image:300:0} |
| A photo column in a table row | {$row.files.photo|insert_image:200:0} |
| Every photo, when an answer has several | {foreach $files.sitePhoto_all as $photo}{$photo|insert_image:300:0}{/foreach} |
- Only images get a
filesentry. Other attachments print their file name only. {$files.<field>}is the first image.{$files.<field>_all}exists only when the answer has more than one; in a table row it's{$row.files.<column>_all}.- A form field actually named
fileskeeps its own tag, andfilesisn't added for it. - Photos are shrunk for print. The originals on the submission are unchanged.
An image from an https:// web address works too: {$logo_url|insert_image:250:50}. If an image can't be loaded, the tag prints as written and the tag check warns you.
Print a tag as text
Everything between {literal} and {/literal} prints exactly as written, tags included. Use it when the PDF must show a tag, or a { that would otherwise start one:
{literal}{$this_is_not_a_tag}{/literal}
{assign}, {math}, {include}, barcodes and QR codes aren't supported and print as written.
Modifiers
These are all the modifiers there are. Any other name used as a modifier prints the whole tag as written (UNKNOWN_MODIFIER).
Text
| Modifier | Settings | Example | Result |
|---|---|---|---|
upper, lower | {$name|upper} | ALEX MORGAN | |
ucfirst | {$name|ucfirst} | First letter in capitals | |
ucwords, capitalize | capitalize also takes: capitalize words that contain digits (false), lower-case the rest of each word first (false) | {$site|capitalize} | Harbour Bridge Maintenance |
trim | Characters to remove (optional, ranges like 0..9) | {$code|trim:"0..9"} | Spaces, or the listed characters, removed from both ends |
strip | Replacement (default a space) | {$notes|strip} | Runs of spaces and line breaks collapsed |
truncate | Length (80), ending (...), break words (false), cut the middle (false) | {$notes|truncate:40:"…"} | Cut to 40 characters, ending included, at a word break unless break words is true. A length of 0 prints nothing |
wordwrap | Width (80), line break, cut long words (false) | {$notes|wordwrap:40} | Lines wrapped at 40 characters. Long words stay whole unless cut is true |
indent | Characters (4), fill (space) | {$notes|indent:4} | Every line indented |
cat | Text or values to add on | {$first|cat:" "|cat:$last} | Alex Morgan |
replace | Find, replace with (nothing) | {$name|replace:"Inc":"Pty Ltd"} | Literal find and replace |
regex_replace | A /pattern/flags and what to replace it with (nothing) | {$id|regex_replace:"/[^0-9]/":""} | Only the digits. A pattern that isn't valid leaves the text unchanged |
substr | Start, length (negative counts from the end) | {$id|substr:-4} | Last four characters |
spacify | Spacer (space) | {$code|spacify:"-"} | A-B-C |
string_format | A format such as %d, %s, %f or %04d | {$qty|string_format:"%04d"} | 0042. A missing or invalid format prints the value unchanged |
default | One or more fallbacks | {$nickname|default:"Not provided"} | The first fallback that isn't empty, when the value is empty |
nl2br | Self-closing line breaks (true) | {$notes|nl2br} | Line breaks kept |
escape | Type (html), double-encode (true) | {$url|escape:"url"} | Encoded text. Types: html, htmlall, url, urlpathinfo, quotes, hex, hexentity, decentity, javascript, mail, nonstd; any other type leaves the text unchanged |
unescape | Type (html) | {$html|unescape} | Decoded text. Types: html, htmlall, entity, url |
strip_tags | Replace with a space (true) | {$html|strip_tags} | HTML tags removed |
count_characters | Include spaces (false) | {$notes|count_characters} | A number |
count_words, count_sentences, count_paragraphs | {$notes|count_words} | A number. Sentences end in ., ? or !; paragraphs are separated by line breaks |
Numbers, dates and ticks
| Modifier | Settings | Example | Result |
|---|---|---|---|
number_format | Decimals (0), decimal point (.), thousands (,) | {$hours|number_format:2} | 1,234.50. Halves round away from zero; text that isn't a number prints 0 |
currency | As number_format, 2 decimals by default | {$amount|currency} | $1,234.50, and -$1,234.50 for a negative amount. Always a $ sign |
pad | Width, padding character (space), added on the left | {$docket_no|pad:5:"0"} | 00042. No change when the value is already as wide |
phone_format | Pattern ((%3) %3-%4); each %N takes the next N digits | {$phone|phone_format:"%2 %4 %4"} | 02 9876 5432. Too few digits prints the number unchanged |
date_format | Format (%b %e, %Y), fallback date | {$submitted_at|date_format:"%d/%m/%Y"} | 27/09/2026. An empty or zero date prints nothing |
age | {$date_of_birth|age} | Whole years to today. A future date gives 0; an unreadable one prints nothing | |
checkbox | {$ppe_ok|checkbox} | ☑ when set, ☐ when not | |
checkmark | {$ppe_ok|checkmark} | ✓ when set, nothing when not | |
insert_image | Width, height in pixels; 0 works it out from the picture (see Size a picture) | {$signature|insert_image:200:80} | The picture, from a signature, drawing, files photo or https:// address |
date_format takes % codes (%d, %m, %Y, %b, %e, %H, %M) or letter codes (d/m/Y, j M Y), and shows times in your organization's time zone. "now" as the fallback prints the time the PDF was made.
Lists
| Modifier | Settings | Example |
|---|---|---|
sort | Column (optional), asc (default) or desc | {foreach $crew|sort:"worker_name":"desc" as $row} |
multisort | Pairs of column and direction | {foreach $crew|multisort:"role":"asc":"worker_name":"asc" as $row} |
Both keep items that compare equal in their original order, and leave anything that isn't a list unchanged.
Turn off smart quotes in Word
Word replaces the straight quotes you type with curly ones, and a modifier setting in curly quotes prints as written. Turn the replacement off before you type tags:
- In Word, select File > Options > Proofing, then AutoCorrect Options.
- On the AutoFormat As You Type tab, under Replace as you type, clear "Straight quotes" with “smart quotes”.
- Select OK.
In Word for Mac the same check box is under Word > Preferences > AutoCorrect > AutoFormat As You Type.
This only changes quotes you type from then on. To straighten the quotes already in the template, leave the check box cleared, select Replace on the Home tab, type " in both Find what and Replace with, and select Replace All. Then do the same with '. This straightens every quote and apostrophe in the document, not only the ones in tags.

