Merge Field Syntax
How merge tokens work in ZeroExport — pulling record fields, traversing relationships, custom fields, and rolling up child records with aggregates.
Merge fields are placeholders you type into a template. At generation time ZeroExport replaces each one with a value from the Salesforce record the document is generated for.
You can type a merge field into any text field in the builder — a Heading, Para, or Text element, a Table cell, a Link label or URL, and list items. Drag a field from the Merge Fields panel, or type the token by hand.
Basic token
A data merge field uses double curly braces and a Source.Field path:
{{Account.Name}}
{{Opportunity.Amount}}
{{Contact.Email}}
The part before the dot is the data source alias (usually the object name), and the part after is the field API name.
Use case: Invoice header
H1: {{Account.Name}}
Para: Invoice for {{Opportunity.Name}}
At generation time this becomes:
Acme Corporation
Invoice for Q3 Renewal
Relationship fields
Follow lookups by chaining field names with dots — the same way you would in SOQL:
{{Opportunity.Account.Name}}
{{Contact.Account.Owner.Email}}
{{Case.Contact.MailingCity}}
There is no depth limit imposed by the syntax; you can traverse as many related objects as the record graph allows.
Custom fields and custom relationships
Custom fields work exactly like standard ones — use the API name including the __c suffix:
{{Opportunity.Discount_Percent__c}}
{{Account.Region__c}}
For a custom relationship, use the __r relationship name to cross into the related record:
{{Opportunity.Primary_Contact__r.Name}}
As a convenience, when you write a __c field in the middle of a path, ZeroExport also resolves the matching __r relationship automatically, so both of these reach the related record's name:
{{Invoice__c.Account__r.Name}}
{{Invoice__c.Account__c.Name}}
Case-insensitive matching
Field and alias names are matched case-insensitively. {{account.name}}, {{Account.Name}}, and {{ACCOUNT.NAME}} all resolve to the same value, and so do aggregate names such as sum and SUM. Using the exact API-name casing is still recommended for readability.
Automatic field-mapping validation
In most document tools, a template is only as correct as the person who built it. Every field mapping has to be checked by hand, and a stale mapping (a field that was renamed, removed, or never queried) quietly prints blank in a document someone has already sent.
ZeroExport validates the mappings for you. Every time you save or upload a template, it checks each {{Alias.Field}} token against the template's Data Sources, so a broken or stale mapping is caught before the template can generate a single document.
A token is rejected when Alias is not one of the template's data sources, or Field is not in that source's field list. Generation reads only the fields each data source queries, so such a token would always print blank. The error names the token and the field to add in Data Sources, so the fix is one step.
Because the check runs on every save, it also catches mappings that went stale later: remove a field from a data source and the next save flags every token that still uses it.
The check allows everything that does resolve:
Idand the row numberindexon any data source- a parent lookup's fields through the primary source, such as
{{Opportunity.Account.Name}}when the Account lookup selectsName - a lookup written as its field, such as
{{Contact.Manager__c.Name}} - aggregates such as
{{OpportunityLineItems.sum(TotalPrice) | 0}}, as long as the data source and field are declared. A cross-level field such as{{Orders.sum(Lines.TotalPrice)}}must be declared on the nested source
Matching ignores case. A template with no data source cannot use field tokens.
A data source name on its own, such as {{Contacts}} or {{Account}}, is also rejected. Use a field ({{Contacts.Name}}) or an aggregate ({{Contacts.count}}) instead. In templates saved before this check existed, a token that reaches a whole record or list prints nothing rather than raw rows.
Brackets, system variables and formats are checked too
The same save also checks the shape of every token, so a typo can't reach a document as broken literal text. A save is rejected with an error that quotes the text, for:
| Problem | Example |
|---|---|
A {{ with no closing }} | {{OpportunityLineItems.UnitPrice |
A }} with no opening {{ | Total}} |
A single { with no closing } | a half-typed {toda |
| A single-brace name that is not a system variable | {todya} (the error lists the valid names) |
| Three braces in a row | {{{Account.Name}}} |
| A trailing pipe with no format after it | {{Opportunity.CloseDate | }} |
| A pipe without a space on each side | {{Amount|currency}} |
A lone } is plain text. The pattern after | (such as 0,0.00 or MM/dd/yyyy) is not validated, since formats are freeform.
Showing merge syntax as plain text
To print a literal brace instead of a token, put a backslash before it: \{ prints { and \} prints }. Escaped braces are skipped by validation and are never merged, so you can document your own syntax inside a template:
\{\{Account.Name\}\} → {{Account.Name}}
\{today\} → {today}
A backslash that is not directly before a brace is left as it is. Escapes are not supported inside aggregate tokens ({{Alias.sum(Field)}}).
Missing or blank values
If a field is empty, or there is no related record ({{A.B.C}} where B is null), the token renders as empty text — it does not print the raw {{...}} braces and does not throw an error. This keeps a document clean when optional fields are blank.
Tip: If a token unexpectedly renders empty, confirm the field is included in the data source's field list. Templates saved before the save-time check can still hold a token for a field that was never queried.
Repeating rows and lists
When a token points at a collection of child records (for example, the line items under an opportunity), the element that contains it repeats once per record:
- A Table row containing
{{OpportunityLineItem.Product2.Name}}expands into one row per line item. - A List item containing a child token expands into one bullet per record.
Static rows above the first repeating row (such as a header row) are kept as-is. Nested parent–child hierarchies expand at each level, so a table can show each opportunity followed by its own line items.
Aggregates (roll-ups)
Aggregate tokens summarize a collection of child records into a single value. They use a function name in place of the field:
| Token | Result |
|---|---|
{{OpportunityLineItem.count}} | Number of records |
{{OpportunityLineItem.sum(TotalPrice)}} | Sum of a field |
{{OpportunityLineItem.min(UnitPrice)}} | Smallest value |
{{OpportunityLineItem.max(UnitPrice)}} | Largest value |
{{OpportunityLineItem.avg(UnitPrice)}} | Average value |
Use case: Invoice total row
| Description | Amount |
|-------------|-------------------------------------|
| Line items | {{OpportunityLineItem.count}} |
| Total | {{OpportunityLineItem.sum(TotalPrice)}} |
Cross-level aggregates
You can sum a grandchild field across every parent by using a dotted field path inside the function. This totals the line-item prices across all opportunities:
{{Opportunities.sum(OpportunityLineItem.TotalPrice)}}
Aggregates are computed against the records actually returned for the document, and are re-scoped correctly inside repeating tables — a per-opportunity subtotal sums only that opportunity's own line items.
See also
- System Variables —
{today},{now}, and org/user tokens - Formatting Merge Fields — dates, numbers, currency, and percentages