Skip to content

Template syntax

Variables are placeholders in your template that get replaced with data from each record: a row of an Excel table, a SharePoint list item, a Dataverse row, a row of a Google sheet, a Lark Base record, or the JSON you send through the document API. The syntax is the same whatever the source.

Use curly braces with the exact field name:

{Field_Name}

Examples:

  • {Customer_Name} → “Acme Corp”
  • {Invoice_Date} → “2024-01-15”
  • {Total_Amount} → “1,500.00”

With no filter, a placeholder prints the value the way its source shows it. Number and date filters work on the raw value underneath instead: see Filters read the raw value.

Value Excel SharePoint list Dataverse Google Sheets Lark Base Document API
Number As the cell shows it 1,234.5, with the column’s decimal places Dataverse’s formatted text As the cell shows it The field’s number format As sent: 1234.5
Currency As the cell shows it Usually in the column’s currency: $1,234.50 Dataverse’s formatted text, with the currency symbol As the cell shows it In the field’s currency: $1,234.50 As sent
Percent As the cell shows it: 12% 12% As a number As the cell shows it: 12% A Progress field, usually: 12% As sent
Date, time As the cell shows it 2026/02/26, or 2026/02/26 15:30 with a time 2026/02/26, or 2026/02/26 15:30 with a time As the cell shows it The field’s date format, 2026/02/26 by default As sent
Person Text The name A lookup that prints the name People chips: the names The name As sent
Link =HYPERLINK(…): the address The address The address A cell that is one link: the address The address As sent
Choice Text The label; several joined with commas The label; several joined with commas The option; several as the cell shows them The option; several joined with commas As sent; a list joined with commas
Yes/no true / false true / false The column’s own labels, such as Yes / No A checkbox: true / false A checkbox: true / false true / false
Pictures, files Picture columns Picture and Image columns, and the item’s {Attachments} Image and file columns, notes File chips, links to Drive files, =IMAGE(…) Attachment fields Picture URLs
Linked rows Related tables Lookup columns Lookups and relationships Related tabs Link and Lookup fields Objects and lists
  • Dates from SharePoint, Dataverse and Lark Base print in the template’s timezone (Template → Output → Timezone, UTC when none is set).
  • An empty cell or field prints nothing, and counts as empty in {#if}.
  • Pictures and files print nothing as text. Place them through a picture’s Alt Text (see Dynamic images), or add them to a PDF with {ATTACH:…}.
  • Linked rows are reached with dot notation or a loop: see Linked records.
  • Links and people have sub-properties, such as {Website.text} and {Owner.email}: see Rich field properties.
  • Lark Base: a Formula field prints by its result type (text, number or date), a Rating field usually prints 4/5, and a Lookup field prints the values it looks up.

Each source’s own page has more detail: the Excel add-in, SharePoint lists and Dataverse, the Google Sheets add-on and the document API.

Number and date filters ignore how a value prints and re-format the raw value underneath:

{Price} → "$1,234.50" (as the source shows it)
{Price | number:2} → "1,234.50" (raw number, re-formatted)
{Price | currency:"EUR":"de-DE"} → "1.234,50 €" (raw number, German locale)
{Date} → "2026/02/26" (as the source shows it)
{Date | date:"DD MMM YYYY"} → "26 Feb 2026" (raw date, re-formatted)

The raw value is also what sum, avg, max, min, where and sort compare. What it is depends on the source:

Source Raw number Raw date
Excel The cell’s value, whatever its number format The day and time the cell holds, in the template’s timezone
SharePoint list The stored number A date-only column: that day, in the template’s timezone. A date and time: the moment stored
Dataverse The stored number A date-only or time-zone independent column: the day and time it holds, in the template’s timezone. Any other: the moment stored
Google Sheets The value under the cell’s format The day and time the cell holds, in the template’s timezone
Lark Base The field’s number The moment stored
Document API A JSON number, or text that is a plain number, such as "1234.5" Text such as 2026-02-26 or 2026-02-26 15:30, read in the template’s timezone. Text with a zone, such as 2026-02-26T15:30:00Z, as written. A number, as milliseconds since 1 January 1970 (UTC)
  • A percent’s raw value is a fraction: 12% is 0.12, and percent multiplies it by 100. Through the document API, send 0.12, not 12.
  • A value with no raw number prints as it is, with a warning, when a number filter is applied. That includes text with separators such as "1,234.50", a Lark Rating field, and a Lark Lookup of more than one number.
  • In Excel and Google Sheets, a cell that holds only a time or a duration is text: it prints as shown and is not a date.
  • An Excel workbook set to the 1904 date system prints its dates right. When AutomaDoc reads it from OneDrive or SharePoint (a template bound to the file), its raw dates are four years and a day early.

Linked records come from a related table (Excel), a lookup (SharePoint, Dataverse), a related tab (Google Sheets), a link field (Lark Base) or a nested object (document API). Access their fields using dot notation:

{Customer.Name}
{Customer.Address}
{Customer.Email}

Nesting works up to 5 levels deep:

{Order.Customer.Company.Address.City}

Links and people expose sub-properties through dot notation, and so do some Lark Base field types. Without dot notation, they print their default value.

A link from an Excel =HYPERLINK(…) cell, a SharePoint hyperlink column, a Google Sheets cell that is one link, or a Lark URL field:

{Website} → "https://example.com" (the address)
{Website.text} → "Example Site" (the words shown)
{Website.link} → "https://example.com" (the address)

In Google Sheets, a cell with a link inside other text prints that text. In Excel, a link added with Insert → Link prints its words.

One person, from a SharePoint person column or a Lark Created By or Modified By field:

{Owner} → "John Doe" (the name)
{Owner.name} → "John Doe"
{Owner.email} → "john@example.com" (SharePoint)
{Owner.en_name} → "John Doe" (Lark Base: the English name)
{Owner.id} → the person's ID in the source; in Lark Base, a Lark user ID such as "ou_abc123"

Where several people fit, the value is a list even when it holds one person, so {Assignee.name} resolves to nothing: a Lark User field, a Google Sheets cell of people chips, or a SharePoint person column that allows several people. Use an index for a specific person, or a loop for all of them:

{Assignee} → "John Doe, Jane Roe" (all names)
{Assignee[0].name} → "John Doe" (first person)
{Assignee[0].email} → "john@example.com" (SharePoint, Google Sheets)
{#Assignee}
{name}
{/Assignee}

In Dataverse, people (an owner, a “created by”) are lookups: {Owner} prints the name, and {Owner.Field} reads a column of that person’s row, as for any linked record.

{Office} → "Headquarters, 123 Main St, City, Country"
{Office.name} → "Headquarters"
{Office.full_address} → "123 Main St, City, Country"
{Office.address} → "123 Main St"

A Group field is a list, so index it or loop over it. {Team.name} resolves to nothing:

{Team} → "Engineering, Design" (comma-separated names)
{Team[0].name} → "Engineering" (first group)
{Team[0].id} → first group ID

For all groups, use loops:

{#Team}
{name} (ID: {id})
{/Team}

A Lark Multi Select field, a SharePoint choice or managed metadata column with several values, a Dataverse multi-select choice, or a Google Sheets multi-select dropdown can be looped or joined with a separator of your own:

{Tags} → "Red, Blue, Green" (default comma-separated)
{Tags | join:" / "} → "Red / Blue / Green"
{#Tags}
- {.}
{/Tags}

Through the document API, a list of plain values arrives already joined with commas, so {Tags} prints Red, Blue, Green and cannot be looped. To loop, send a list of objects.

For fields that link to multiple records, use loop syntax to repeat content for each item:

{#Line_Items}
Product: {Product_Name}
Quantity: {Quantity}
Price: {Price}
{/Line_Items}

{#Field} starts the loop, {/Field} ends it. Inside loops, use field names without the parent prefix.

You can use {/} instead of repeating the field name:

{#Line_Items}
{Product_Name} - {Price}
{/}

When loop tags are placed inside a table row, the entire row is duplicated for each item, which is perfect for line-item tables:

Product Qty Price
{#Line_Items} {Product_Name} {Quantity} {Price} {/Line_Items}

For arrays of simple values (not objects), use {.} to reference the current item:

{#Tags}
{.}
{/Tags}

Inside any loop, these special variables are available:

Variable Description Example
{$index} Current index (starts at 0) 0, 1, 2, …
{$num} Row number (starts at 1) 1, 2, 3, …
{$first} true on the first item Prints true / false
{$last} true on the last item Prints true / false

Loops can be nested. Inner loops can access fields from outer scopes:

{#Departments}
Department: {Name}
{#Employees}
{Name} works in {Department.Name}
{/Employees}
{/Departments}

If a field name exists in both the inner and outer scope, the inner scope takes priority.

Add filters to the opening tag of a loop to choose which rows it repeats, and in what order:

{#Line_Items | where:"Status == 'Approved'" | sort:"Amount":"desc" | limit:10}
{Product_Name} {Amount | currency:"USD"}
{/Line_Items}
Filter What it does Example
where Keeps only the rows that match a condition where:"Status == 'Approved'"
sort Orders the rows by a field, smallest or earliest first sort:"Due_Date"
sort … or largest or latest first sort:"Amount":"desc"
limit Keeps the first rows limit:10
offset Skips the first rows offset:10
unique Keeps the first row for each value of a field unique:"Customer"

Filters apply left to right, in the order you write them: sort:"Amount":"desc" | limit:3 is the three largest rows, while limit:3 | sort:"Amount":"desc" is the first three rows, largest first.

A where condition goes inside the double quotes, with any text value in single quotes:

Condition Keeps the rows where
Status == 'Approved' Status is exactly Approved
Status != 'Draft' Status is anything but Draft
Amount > 1000 Amount is more than 1000 (also >=, <, <=)
Paid Paid has a value, as in {#if Paid}
!Notes Notes is empty
  • Numbers are compared by their raw value, not by how they print: Amount > 1000 matches $1,250.00. Write the number plainly: 1000, not 1,000.
  • Dates are compared as dates when you write them as 2026-01-31 (or with a time, 2026-01-31 17:30), in the template’s timezone: where:"Due_Date >= '2026-01-01'". A whole day counts: Due_Date > '2026-01-31' starts on 1 February.
  • Join conditions with && (and) and || (or), and group them with parentheses: where:"Status == 'Approved' && (Amount > 1000 || Rush)".
  • sort puts dates in date order, numbers in number order and text in alphabetical order. Empty cells always go last.
  • Several where filters must all match. Several sort filters written one after another sort by the first, then by the next where the first is equal: sort:"Category" | sort:"Amount":"desc".
  • Linked record fields work with dot notation: where:"Product.Category == 'Hardware'".
  • For a list of plain values, such as a Multi Select field, use . for the value itself: {#Tags | where:". != 'Internal'" | sort}.
  • Filters work in table row loops, paragraph loops, nested loops and Lark Approval detail lists alike.

sum, count and join take the same filters, so a total can add up exactly the rows the table above it shows:

{Line_Items | where:"Status == 'Approved'" | sum:"Amount" | currency:"USD"}
{Line_Items | where:"Status == 'Approved'" | count} approved items

If a filter in a total cannot be applied, the total is left blank with a warning, rather than adding up every row.

Add horizontal to a loop inside a table to repeat its cells across, one column per item, instead of repeating the row down:

Label Repeated across
Name {#Staff | horizontal} {Name}
Role {Role} {/Staff}

With three people this becomes a table with a column for each of them. Every row between the opening and closing tags is repeated together, so the name and role of each person stay in the same column.

  • The new columns share the width of the template cell evenly, and keep its borders and shading.
  • It combines with the other filters: {#Staff | where:"Active" | sort:"Name" | horizontal}.
  • Up to 20 columns. Items beyond that are left out, with a warning.
  • If the list is empty, the cell is left blank rather than removed.
  • A horizontal loop can sit inside a row loop: each row then gets as many columns as its own list has items.
  • Outside a table, horizontal is ignored with a warning and the loop repeats as usual.

Show or hide content based on field values.

Show content only if a field has a value:

{#if Discount}
Discount applied: {Discount}
{/if}

Show content only if a field is empty:

{#if !Notes}
No additional notes.
{/if}
{#if Status == "Approved"}
This invoice has been approved.
{/if}
{#if Status != "Draft"}
This document has been submitted.
{/if}

>, >=, < and <= compare numbers by their value and dates as dates (written 2026-01-31, in the template’s timezone):

{#if Amount > 1000}
Requires a second approval.
{/if}
{#if Due_Date < "2026-01-01"}
Overdue.
{/if}

== and != in {#if} compare the text as it prints: {#if Qty == "2"} does not match a quantity shown as 2.00. In a where filter or a {#Qty == 2}…{/} section, they also match the same number or the same day.

Filters let you transform how values are displayed using the pipe (|) syntax:

{Field | filterName}
{Field | filterName:"arg1":"arg2"}

Filters are applied after the value is retrieved and before it’s inserted into the document. With no filter, the value prints as its source shows it (see What a placeholder prints).

Filter Description Example Output
upper Uppercase {Name | upper} ACME CORP
lower Lowercase {Name | lower} acme corp
capitalize Capitalize each word {Name | capitalize} Acme Corp
trim Remove leading/trailing whitespace {Name | trim} Acme Corp
truncate Truncate to length (default: 50) {Description | truncate:16} Lorem ipsum dolo...
pad Pad with character (default: 0) {$num | pad:3} 001

number, currency and percent accept an optional locale for region-specific formatting:

Filter Args Example Output
number decimals, locale {Price | number:2} 1,234.50
number decimals, locale {Price | number:2:"de-DE"} 1.234,50
currency currency code, locale {Price | currency:"USD"} $1,234.50
currency currency code, locale {Price | currency:"EUR":"de-DE"} 1.234,50 €
currency currency code, locale {Price | currency:"JPY":"ja-JP"} ¥1,235
currency currency code, locale, decimals {Price | currency:"IDR":"id-ID":2} Rp 1.234,50
percent decimals, locale {Rate | percent:1} 45.6%
percent decimals, locale {Rate | percent:1:"de-DE"} 45,6 %
toFixed decimals {Price | toFixed:2} 1234.50 (no thousands separator)
chineseUppercase None {Amount | chineseUppercase} 捌万捌仟捌佰捌拾捌
chineseLowercase None {Amount | chineseLowercase} 八八八八八
chineseFinancial None {Amount | chineseFinancial} 捌万捌仟捌佰捌拾捌元整

Format dates with custom patterns, when the format a date has in your data is not the one you need.

Syntax: {Field | date:"format":"timezone":"locale"}

All args are optional. Timezone defaults to the template’s own (Template → Output → Timezone), UTC when none is set. Locale defaults to en-US. A date sent as text with no time zone, such as 2026-01-31 from the document API or a Lark Approval form, prints as that day whatever the timezone.

Filter Args Example Output
date format, timezone, locale {Date | date:"DD MMM YYYY"} 26 Feb 2026
date format, timezone, locale {Date | date:"dddd, DD MMMM YYYY"} Thursday, 26 February 2026
date format, timezone, locale {Date | date:"DD MMMM YYYY":"UTC":"fr-FR"} 26 février 2026
date format, timezone, locale {Date | date:"dddd, DD MMMM YYYY":"UTC":"de-DE"} Donnerstag, 26 Februar 2026

Supported date tokens:

Token Output Example
YYYY 4-digit year 2026
MMMM Full month name (locale-aware) February, février, Februar
MMM Short month name (locale-aware) Feb, févr., Feb
MM 2-digit month 02
dddd Full weekday name (locale-aware) Thursday, Donnerstag, 木曜日
ddd Short weekday name (locale-aware) Thu, Do, 木
DD 2-digit day 26
HH 24-hour hour 15
mm Minutes 30
ss Seconds 00

Examples:

{Invoice_Date | date:"DD MMM YYYY"} → 26 Feb 2026
{Invoice_Date | date:"YYYY-MM-DD"} → 2026-02-26
{Invoice_Date | date:"MM/DD/YYYY"} → 02/26/2026
{Invoice_Date | date:"DD/MM/YYYY":"Asia/Tokyo"} → 26/02/2026 (in Tokyo timezone)
{Invoice_Date | date:"dddd, DD MMMM YYYY":"UTC":"ja-JP"} → 木曜日, 26 2月 2026
{Invoice_Date | date:"DD MMMM YYYY":"UTC":"id-ID"} → 26 Februari 2026
Filter Description Example Output
join Join array values (default: , ) {Tags | join:" / "} Red / Blue / Green
join Join one field of each row, leaving out empty ones {Items | join:", ":"Name"} Widget, Gadget
sum Add up a field across the rows of a loop {Items | sum:"Price"} 1234.5
sum The total, formatted by the next filter {Items | sum:"Price" | currency:"USD"} $1,234.50
count Number of rows in a loop {Items | count} 12
avg Average of a field across the rows (empty cells left out) {Items | avg:"Price"} 411.5
max / min Largest / smallest value of a field across the rows {Items | max:"Price"} $980.00
default Fallback for empty values {Phone | default:"N/A"} N/A

Filters can be chained left-to-right using multiple pipes:

{Name | trim | upper} → " hello " becomes "HELLO"
{$num | pad:3} → 1 becomes "001"
{Phone | default:"N/A" | upper} → null becomes "N/A"

Filters work with loop variables and the current item reference:

{#Items}
#{$num | pad:3} - {Name | upper}
{/Items}
{#Tags}{. | capitalize}, {/Tags}

Replace a placeholder image with a picture from your data: a picture column of an Excel table or a SharePoint list, a Dataverse image column, a file chip or =IMAGE(…) cell in Google Sheets, a Lark attachment field, or a picture URL sent to the document API. The table above lists what each source offers.

  1. Insert any image in your Word template (this is the placeholder).
  2. Right-click the image → Edit Alt Text.
  3. In the Alt Text description, type {FieldName}.
  4. Save your template.

The placeholder image will be replaced with the picture from that field, preserving the placeholder’s size.

For fields with multiple attachments, use index notation:

{Photo} → First image (same as {Photo[0]})
{Photo[1]} → Second image
{Photo[2]} → Third image

For fields with a variable number of attachments, use loop syntax with {.} as the image alt text. Each attachment expands its own copy of the placeholder:

{#Product_Photos}
[image with alt text: {.}]
{/Product_Photos}

In a table, each attachment gets its own row:

# Photo Filename
{#Product_Photos} {$num} [image alt: {.}] {name} {/Product_Photos}

Available properties inside the loop:

Variable Description Example
{name} Filename photo1.jpg
{type} MIME type image/jpeg
{size} File size in bytes 204800

Image placeholders work inside loop blocks. Each iteration resolves the image for that item:

{#Employees}
Name: {Name}
Photo: [image with alt text: {Photo}]
{/Employees}

Generate QR codes and barcodes from field values. Set up the same way as dynamic images, via image Alt Text.

{QR:FieldName}

Example: {QR:Order_ID} generates a QR code containing the Order_ID value.

Syntax Type Notes
{BARCODE:FieldName} Code128 Default, alphanumeric
{BARCODE128:FieldName} Code128 Explicit Code128
{BARCODE39:FieldName} Code39 Uppercase + digits only
{BARCODEEAN13:FieldName} EAN-13 13-digit retail product code
{BARCODEEAN8:FieldName} EAN-8 8-digit short-form EAN
{BARCODEUPCA:FieldName} UPC-A 12-digit North American retail
{BARCODEUPCE:FieldName} UPC-E Compressed UPC
{BARCODEITF14:FieldName} ITF-14 Shipping / carton codes
{BARCODEDATAMATRIX:FieldName} DataMatrix 2D, high density
{BARCODEPDF417:FieldName} PDF417 2D, stacked linear

The prefix is case-insensitive: {qr:Field} and {QR:Field} both work.

All placeholder syntax works in document headers and footers, just like in the main body.

Common uses:

  • Company name or logo in the header
  • Document ID or date in the footer
  • Dynamic labels like Invoice for {Customer_Name}

To add placeholders: Insert → Header/Footer → Edit → type your placeholder like {Field_Name}.

Templates written for another Lark document app work here unchanged: upload the Word file as it is. This is what their syntax does in AutomaDoc:

Written as What it does here
{#isNew} Welcome {/isNew} Shows the text when the field has a value. Over a list, it is a loop
{#!isNew}Welcome{/isNew} Shows the text when the field is empty, unticked or an empty list
{#Status=="Paid"}…{/} Shows the text when the condition holds. !=, >, >=, <, <=, && and || work too
{/} Closes the nearest open loop or section
{$index+1} Row number, starting at 1, the same as {$num}
{#Items | sortBy:"Total":"desc"} The same as sort
{#Items | where:"A == 'x' && B > 5"} Keeps the rows that match, as in Filtering, sorting and limiting rows
{>Items}{Name} at the start of a table row Repeats that cell and the cells to its right across the row, one column per item (a horizontal loop)
{#image}{#$isImage}{%src}{/}{#!$isImage}{$fileName}{/}{/image} Prints each picture of an attachment field, and the name of each file that is not a picture
{%src | size:100:50} / {%src | maxSize:500:500} A picture at exactly that size, or no larger than it, in pixels
{%Logo} The first picture of an attachment field, at its own size up to the width of the page
{Name | uppercase} / {Name | lowercase} The same as upper / lower
{Price | toFixed:"2"} The number with two decimals, no thousands separator
{Price | currency:'USD'} Arguments in single quotes work as in double quotes
{Date | customDate:'YYYY-MM-DD HH:mm:ss'} The same tokens as date, in the template’s timezone
{Date | date:'full'} 2024-7-13 Saturday 00:00:00
chineseUppercase, chineseLowercase, chineseFinancial Numbers in Chinese, see Number filters
Syntax Description
{Field} Simple variable
{Field.Nested} Linked record field (dot notation)
{Link.text} / {Link.link} Link sub-properties
{Owner.name} / .email / .id One person’s sub-properties (.en_name in Lark Base)
{Assignee[0].name} Several people (a list, so index or loop it)
{Location.name} / {Location.full_address} Lark Base Location sub-properties
{Team[0].name} / {Team[0].id} Lark Base Group field (a list, so index or loop it)
{#Field}...{/Field} Loop over multi-value field
{#Field | where:"Status == 'Paid'"} Loop over the matching rows only
{#Field | sort:"Amount":"desc"} Loop in order, largest first
{#Field | limit:5} / {#Field | offset:5} First 5 rows / all but the first 5
{#Field | horizontal} Repeat table cells across, one column per row
{Field | where:"Paid" | sum:"Amount"} Total of the matching rows
{Field | count} Number of rows
{Field | avg:"Amount"} / max / min Average, largest, smallest of the rows
{#Field == "v"}...{/} Section shown when the condition holds
{#!Field}...{/Field} Section shown when the field is empty
{#if Field > 100}...{/if} Conditional (number or date order)
{/} Shorthand close (closes nearest open loop)
{.} Current item in loop (for primitive arrays)
{$index} Loop index (0-based)
{$num} Row number (1-based)
{$first} / {$last} First/last item boolean
{#if Field}...{/if} Conditional (truthy)
{#if !Field}...{/if} Conditional (falsy)
{#if Field == "val"}...{/if} Conditional (equality)
{#if Field != "val"}...{/if} Conditional (inequality)
{Field | upper} Filter: uppercase
{Field | lower} Filter: lowercase
{Field | capitalize} Filter: capitalize words
{Field | trim} Filter: strip whitespace
{Field | truncate:30} Filter: truncate with ellipsis
{Field | pad:3} Filter: zero-pad
{Field | number:2} Filter: number with decimals
{Field | number:2:"de-DE"} Filter: localized number
{Field | currency:"USD"} Filter: currency formatting
{Field | currency:"EUR":"de-DE"} Filter: localized currency
{Field | percent:1} Filter: percentage
{Field | date:"DD MMM YYYY"} Filter: custom date format
{Field | date:"dddd, MMMM DD":"UTC":"fr-FR"} Filter: localized date
{Field | join:" / "} Filter: join array with separator
{Field | default:"N/A"} Filter: fallback value
{Field | trim | upper} Chained filters (left-to-right)
{Photo} / {Photo[1]} Image (via Alt Text)
{QR:Field} QR code (via Alt Text)
{BARCODE:Field} Barcode (via Alt Text)