WelisaWelisa DocGen
v1.1.0 welisa.com

Merge tag reference#

Every tag Welisa DocGen recognises, in one place. Tags work identically in Word, HTML, Canvas, Excel and PowerPoint templates unless a section says otherwise. Function names (SUM, COUNT, Today) are case-insensitive; field names are case-sensitive. Null or missing fields render as empty text — no error, no placeholder.

Note

There is no escape or comment syntax: any {…} pair is treated as a tag (an unknown name resolves to empty), and an unclosed { fails the merge with "Malformed merge tag". For literal braces in HTML use { and }; in Word avoid brace-wrapped prose.

Quick reference#

TagWhat it doesExample
{FieldName}Insert a field value{Name}, {Email}
{Parent.Field}Pull from a related record, any depth{Account.Owner.Email}
{Field:format}Format dates, currency, numbers, percent, checkboxes, picklists{Amount:currency:EUR:nl_NL}
{#ChildList}…{/ChildList}Repeat for each child record{#Contacts}{FirstName}{/Contacts}
{RowNumber}1-based row counter inside a loop{#Contacts}{RowNumber}. {Name}{/Contacts}
{#GroupBy List by Field}…{/GroupBy}One block per distinct value{#GroupBy Lines by Product2.Family}{GroupName}…{/GroupBy}
{#Field}…{:else}…{/Field}Show when truthy, with fallback{#Industry}Sector: {Industry}{:else}No industry{/Industry}
{^Field}…{/Field}Show when false or blank{^HasDiscount}No discount{/HasDiscount}
{#IF expr}…{:else}…{/IF}Compare, combine with AND/OR/NOT{#IF Amount > 50000}Premium{:else}Standard{/IF}
{SUM:List.Field} {COUNT:List} {AVG} {MIN} {MAX}Totals over a child relationship{SUM:OpportunityLineItems.TotalPrice:currency}
{%Image:N} {%Field} {%asset:key}Images: attached, from a field, shared{%asset:logo:200x}
{*Field} {*Field:qr}Barcode (Code 128) or QR code{*TrackingUrl:qr:200}
{Chart:Rel:Field:style:opts}A chart as an image{Chart:Responses__r:Answer__c:pie}
{@Signature_Role:Order:Type}E-signature placement{@Signature_Buyer:1:Full}
{Today} {Now}Generation date / date-time{Today:d MMMM yyyy}
{RunningUser.Field}Who generated the documentPrepared by {RunningUser.Name}
{#Approvals}…{/Approvals}Classic approval history{#Approvals}{StepStatus} — {ActorName}{/Approvals}
{PageNumber} {TotalPages}Page counters — header/footer fields onlyPage {PageNumber} of {TotalPages}
{?key}A signer form-field answerPO number: {?poNumber}

Field merge#

{FieldName}
{!FieldName}              Salesforce-style prefix — treated identically
{Account.Name}            parent lookup
{Owner.Profile.Name}      multi-level lookup, any depth

Format specifiers#

Append :format to a field tag.

Dates#

{CloseDate:dd-MM-yyyy}          Java SimpleDateFormat pattern → 17-04-2026
{CloseDate:d MMMM yyyy}         17 April 2026
{CloseDate:date}                the running user's locale default
{CloseDate:date:nl_NL}          17-04-2026 (Dutch)
{CloseDate:date:de_DE}          17.04.2026
{CloseDate:date:en_GB}          17/04/2026

Locale defaults: nl_* → dd-MM-yyyy; en_US → MM/dd/yyyy; en_GB/AU/NZ/IE/IN, fr_*, es_*, it_*, pt_* → dd/MM/yyyy; de_*, ru_*, pl_*, cs_*, hu_*, tr_* → dd.MM.yyyy; ja_* → yyyy/MM/dd; zh_* and Nordic → yyyy-MM-dd; ko_* → yyyy. MM. dd.

Currency#

{Amount:currency}               $500,000.00 (US default)
{Amount:currency:EUR}           €500,000.00
{Amount:currency:EUR:nl_NL}     € 500.000,00 (Dutch formatting)
{Amount:currency:EUR:de_DE}     500.000,00 €
{Amount:currency:JPY}           ¥500000 (zero-decimal currency)
{Amount:currency:GBP}           £500,000.00

Supported: USD, EUR, GBP, JPY, CNY, CHF, CAD, AUD, INR, KRW, BRL, MXN, SEK, NOK, DKK, PLN, CZK, HUF, TRY, ZAR, SGD, HKD, NZD, THB, MYR, PHP, IDR, TWD, ILS, RUB, NGN, KES, AED, SAR, COP, CLP, PEN, ARS, EGP, GHS, ISK, VND. Zero-decimal currencies format without decimals automatically.

Auto-detecting the currency from the record. :currency:auto follows the record's own currency — ideal for multi-currency orgs:

{Amount:currency:auto}                        reads the standard CurrencyIsoCode
{Amount:currency:auto=CustomerCurrency__c}    reads a named ISO-code field
{Amount:currency:auto:nl_NL}                  auto + explicit locale

The source field must hold an ISO 4217 code and must be in the Query Configuration (the standard CurrencyIsoCode is added automatically in multi-currency orgs). Unknown or blank codes fall back to the default $ format. Aggregates support it too: {SUM:Lines.Amount:currency:auto=CustomerCurrency__c} uses the parent record's currency.

Totalling across currencies. Summing rows that carry different currencies and stamping one symbol on the result would be meaningless, so it is refused unless you ask for conversion:

{SUM:Lines.Amount:currency:EUR}              mixed-currency rows → generation fails with a clear error
{SUM:Lines.Amount:currency:EUR:convert}      every row converted to EUR first, then summed
{SUM:Lines.Amount:currency:auto:convert}     converted into the parent record's currency
{Amount:currency:EUR:convert}                a plain field converted from its own currency

:convert always goes last; rates come from Setup → Manage Currencies (static rates, not dated Advanced Currency Management rates); a missing rate fails with an actionable message. Single-currency orgs are unaffected.

Numbers, percent, checkboxes, picklists#

{Quantity:number}               1,234 (US separators)
{Quantity:number:nl_NL}         1.234
{Quantity:number:fr_FR}         1 234
{Quantity:#,##0.00}             custom pattern — always US separators → 1,234.56
{Rate:percent}                  15.5%
{Rate:percent:nl_NL}            15,5 %
{IsActive:checkbox}             [X] when true, [ ] when false (ASCII, any font)
{Status__c:label}               the user-facing picklist label instead of the stored API value

Loops#

Repeat a block for each child record:

{#Contacts}
  {FirstName} {LastName} — {Email}
{/Contacts}

Container auto-expansion. When the loop tags sit inside a table row (or a bulleted/numbered paragraph, or an <li>), the entire row or paragraph repeats instead of only the inner text. This is how line-item tables work: put {#OpportunityLineItems} in the first cell and {/OpportunityLineItems} in the last cell of the data row.

Repeating the column header. In Word, add {RepeatHeader} anywhere inside the header row; the marker is stripped and the row becomes the table's <thead>, reprinted at the top of each section of a very large table. HTML templates use a native <thead>. (The PDF engine does not reliably reprint a header on every page of a large bordered table; it repeats per section.)

Nested loops work at any depth:

{#Opportunities}
  Opportunity: {Name}
  {#OpportunityLineItems}
    · {Product2.Name} × {Quantity}
  {/OpportunityLineItems}
{/Opportunities}

Empty loops render nothing.

{RowNumber}#

Inside any loop, the row's position starting at 1. Counts rendered rows (after the query's filter, sort and limit); restarts for each nested loop and each {#GroupBy} group; counts straight through on very large tables (1…30,000); resolves to nothing outside a loop. If a record has a field literally named RowNumber, the counter wins inside the loop.

| # | Product | Qty |
| {#OpportunityLineItems}{RowNumber} | {Product2.Name} | {Quantity}{/OpportunityLineItems} |

{#GroupBy} — one table per value#

Group a child relationship by a field and repeat the block once per distinct value — 50 product families → 50 tables, with no need to know the values in advance:

{#GroupBy OpportunityLineItems by Product2.Family}
  <h3>{GroupName}</h3>
  <table>
    <tr><td>{#OpportunityLineItems}{Product2.Name}</td><td>{TotalPrice:currency}{/OpportunityLineItems}</td></tr>
    <tr><td>Subtotal</td><td>{SUM:OpportunityLineItems.TotalPrice:currency}</td></tr>
  </table>
{/GroupBy}
  • {GroupName} is the group's value; the inner {#Relationship} loop and the aggregates inside the block only see that group's members.
  • <Field> may be a dot-path on the child (Product2.Family, Owner.Name).
  • Groups render in first-seen order — add ORDER BY <Field> to the relationship in the Query Configuration to alphabetise.
  • Works identically in Word and HTML; an empty relationship renders nothing.

Conditionals#

Boolean and inverse sections#

{#IsActive}Account is active.{/IsActive}
{#IsActive}Active.{:else}Inactive.{/IsActive}
{^Closed__c}Still open.{/Closed__c}

Truthy: Boolean true, non-empty lists, any non-null, non-false, non-empty value. {^Field} is the inverse.

{#IF} comparisons#

Operators >, <, >=, <=, = (or ==), !=. Values can be field references, quoted strings or numbers. String comparison is case-sensitive.

{#IF Amount > 100000}Large deal — requires approval.{/IF}
{#IF StageName = 'Closed Won'}Congratulations!{:else}Keep pushing.{/IF}
{#IF Priority != 'Low'}Escalate this case.{/IF}

AND / OR / NOT#

Word form (AND, OR, NOT, case-insensitive) and symbolic form (&&, ||, !) both work; parentheses control precedence (default: NOT → comparisons → AND → OR). Quoted strings are opaque.

{#IF Amount > 100000 AND Stage = 'Negotiation/Review'}Large deal in negotiation.{/IF}
{#IF (Amount > 100000 OR Strategic__c) AND IsClosed = false}Large or strategic, still open.{/IF}
{#IF NOT IsPrivate__c}Public record.{/IF}

{#IF} blocks nest arbitrarily. Relationship.totalSize returns 0 (never null) for an empty child relationship, so {#IF Work_Tasks__r.totalSize != 0} is the canonical "render this section only if there are rows" check. There is no {#IFNOT} — use {:else} or the inverse section.

Aggregates#

Grand totals across a child relationship, placed outside the loop. All five functions accept any format suffix, including currency:auto and :convert.

{COUNT:OpportunityLineItems}                          1000
{COUNT:OpportunityLineItems:number}                   1,000
{SUM:OpportunityLineItems.TotalPrice:currency:EUR:nl_NL}   € 50.000,00
{AVG:OrderItems.UnitPrice:currency}                   $127.50
{MIN:Quotes.Amount:currency}  {MAX:Deals.Amount:currency:GBP}

Aggregated fields do not need to be rendered columns — you can total UnitPrice even if the table only shows names and quantities; the field is validated against the child object's schema.

Charts#

{Chart:RELATIONSHIP:FIELD:STYLE:OPTS} renders one of nine chart styles as a real image in every output format. The full option set, the output-format matrix and the hand-authored {#ChartBucket} loop are on the Charts page.

Images#

Record-attached (easiest). {%Image:N} renders the Nth oldest image attached to the current record — drag a photo onto the record's Files and the tag picks it up (PNG/JPG/GIF/BMP/TIFF/SVG; other files skipped). Inside a loop it scopes to the iterating record's images: inspection reports, listings, product catalogues.

{%Image:1}              first attached image, natural size
{%Image:1:200}          max 200 px in either dimension
{%Image:1:200x200}      exactly 200 × 200 px
{%Image:1:400x}         400 px wide, auto height
{%Image:1:x150}         150 px tall, auto width

From a field. Store a file Id (starts with 068) in a text field and reference it: {%LogoImage__c}, {%LogoImage__c:200x100}. The tag also understands rich-text <img src="data:…">, raw base64, Salesforce file URLs and HTTPS URLs (PDF only).

Important

In HTML templates a {%…} tag emits its own <img> element. Place it where the image should appear (<div class="hero">{%Header_Image__c}</div>) and size it with CSS on the emitted img or with a size token. Never write <img src="{%Field}">.

Image size limits. PDFs with attached images are limited to roughly 30 MB of total image content for a reliable Save to Record; above that, use Download. Twenty to thirty phone photos fit; fifty high-resolution photos may not.

Shared assets — {%asset:key}#

Use a shared asset when the same image — a logo, a footer band, a letterhead — appears in many templates and you want to update it in one place. Command Hub → Assets: give the asset a name, a short permanent tag key (logo, footer), upload the image, copy its tag. Uploading a new version updates every template that uses the key, with no template edits. Assets have thumbnails, live search, and optional categories.

{%asset:footer}                 latest version of the "footer" asset
{%asset:logo:200x100}           fixed 200 × 100 px
{%asset:logo:600x}              600 px wide, aspect kept
{%asset:logo:x80}               80 px tall, aspect kept

Works across Word, HTML, Canvas, PDF and PowerPoint output, in the body and in headers/footers, and on very large datasets. Excel output removes the tag cleanly. Deactivate an asset rather than deleting it; a tag pointing at a deactivated or unknown key renders a small [missing asset: key] placeholder so you notice it in review. Inside e-mail templates the same tag resolves to the asset's public image URL — write your own <img src="{%asset:footer-banner}" style="height:40px"/>.

Sizing images in HTML to PDF#

The size token on any image tag controls the rendered size in HTML → PDF output. Pixels and percent only, at 96 DPI (:96x ≈ 1 inch, :288x ≈ 3 inches):

TokenResult
:300x100300 × 100 px exactly (may stretch off-aspect)
:300x300 px wide, height auto
:x100100 px tall, width auto
:50%x50 % of the page content width, height auto
:300 (bare){%Image:N} only; on {%Field} / {%asset} a bare number is ignored

Named units (in, pt, cm, mm) are not parsed — convert at 96 px per inch (1 cm = 37.8 px). max-width / max-height are not honoured in PDF. For a row of partner logos with different dimensions, give them all a uniform width (:200x), never a fixed box. If you hand-write <img> tags, the engine computes one size per URL: the same file at two sizes needs distinct query strings (…?n=1, …?n=2) — the {%…} tags do this for you.

Barcodes and QR codes#

{*OrderNumber}                  Code 128 barcode (default)
{*TrackingId:code128}           explicit type
{*SKU:code128:300x80}           300 × 80 px
{*ProductCode:qr}               QR code (150 px default)
{*URL:qr:200}                   200 px QR code

Generated natively — no external services. Word and HTML templates, PDF and DOCX output. Only code128 and qr exist: an unsupported type (code39) renders nothing, silently. QR codes use Level Q error correction and hold up to 600 characters; keep printed values under 120 characters for reliable scanning at one inch square.

Signatures#

{@Signature_Buyer}                  typed full signature (shorthand for :1:Full)
{@Signature_Buyer:1:Full}           role = Buyer, order = 1, type = full signature
{@Signature_Buyer:1:Initials}       initials
{@Signature_Buyer:1:Date}           auto-filled signing date
{@Signature_Buyer:1:DatePick}       a date the signer chooses
{@Signature_Loan_Officer:2:Full}    multi-word roles use underscores
{@Signature_Buyer:1:Full:inline}    compact in-place mark, no stamp card
  • Role — any string; underscores become spaces in the UI. The template owns the role names: the Send for Signature action pre-builds one signer per role found in the template.
  • Order — sequence per role (default 1); used for sequential signing and multiple placements per signer.
  • Type — Full, Initials, Date, DatePick (default Full).
  • :inline — render only the ink, no card or caption, for tight layouts.

Before signing, tags are preserved in the output. After signing, each position carries the signer's mark as a stamp card (drawn ink or typed name, with a "Signed by … · date" caption). Give every tag its own line or cell. Details: E-signatures.

{#Signatures} — variable signer count#

Role tags pin each signer to a fixed spot. When the number of signers varies, author one layout that repeats once per signer:

{#Signatures}
  ______________________________
  {Name}
  Electronically signed on {SignedDate}
  {Role} · {Email}
{/Signatures}

Fields inside the block: {Name} (the name typed at signing, or the invited name), {RegisteredName}, {Role}, {Email}, {SignedDate} (blank before signing), {Status} (Pending / Signed). Additive — role tags keep working and both can be combined. Populated by the snapshot signing flow (the one that re-renders the document from send-time data on completion).

{?key} — signer form-field answers#

A template's Form Fields (text, number, date, checkbox, picklist) collect input during signing; {?poNumber} prints the answer, {?poNumber|N/A} with a fallback. Give the tag its own cell or line. See Signer form fields.

Rich text fields#

When a field value contains HTML (<p>, <br>, <b>, <i>, <u>, <span>, <img>, <a>…), the formatting carries through — paragraphs, line breaks, emphasis, hyperlinks, embedded images — in Word (DOCX or PDF) and HTML (PDF) templates. PowerPoint strips to plain text. Plain multiline fields render their newlines as line breaks; no manual <br> needed.

Inline images pasted into a Rich Text Area render in all three targets when generated from the runner. Caveats: Generate Sample in the template builder shows them as broken placeholders (test through the runner); rotation is not preserved; and Lightning's editor never stores a size, so a 4000 × 3000 phone photo renders at 4 inches in DOCX but at natural pixel size in PDF — resize before pasting, or prefer {%Image:N} with a size token.

Watermarks and page backgrounds#

Option A — the template's Watermark tab (recommended). Upload a pre-sized image and pick a strength (Light 15 % / Medium 30 % / Strong 50 % / Original). The opacity is baked into the stored image because the PDF engine has no CSS opacity; the unfaded original is kept so changing the strength later re-fades from it. Applies immediately.

Option B — Word's Design → Watermark dialog. Works with constraints: scale 100 % (other scales are ignored), Washout off (pre-fade the image yourself), no rotation (pre-rotate the PNG).

Both render as a full-bleed page background. Pre-size the image to the page at 96 DPI: Letter 816 × 1056 px, A4 794 × 1123 px, Legal 816 × 1344 px. Text watermarks ("DRAFT") are not supported — render the text into a PNG. DOCX output keeps whatever Word renders natively.

Built-in date and time#

{Today}                         bare tag carries a midnight time — add a format
{Today:dd-MM-yyyy}              20-04-2026
{Today:d MMMM yyyy}             20 April 2026
{Today:date:nl_NL}              20-04-2026
{Now:yyyy-MM-dd HH:mm}          2026-04-20 14:30

Case-insensitive ({today} works); all format suffixes apply; works everywhere including bulk, large datasets and signed documents.

Running user#

{RunningUser.X} resolves against the user who clicks Generate, runs the Flow or owns the bulk job. Only an allow-list of User fields is exposed, so a template can never leak arbitrary user data:

GroupFields
IdentityId, Name, FirstName, LastName, Email, Username, Alias
Title / organisationTitle, Department, CompanyName, EmployeeNumber
ContactPhone, MobilePhone, Extension, Fax
AddressStreet, City, State, PostalCode, Country
LocaleTimeZoneSidKey, LocaleSidKey, LanguageLocaleKey

The user row is queried once per transaction and cached, so {RunningUser.Name} in the header of a 60,000-row PDF costs one extra query.

Checkmarks and symbols#

The PDF engine ships four fonts and cannot load Wingdings, Symbol or any custom font. Welisa DocGen handles symbols two ways: Word checkbox glyphs (Wingdings checkboxes and content-control checkboxes) are translated to Unicode at render time, and Unicode symbols typed directly are wrapped in Arial Unicode MS automatically so they render instead of vanishing.

SymbolNameCode point
☐ ☑ ☒empty / checked / crossed checkboxU+2610, U+2611, U+2612
✓ ✔ ✗ ✘check mark, heavy check, ballot X, heavy XU+2713, U+2714, U+2717, U+2718
• ▪ ▶ ►bullet, black square, trianglesU+2022, U+25AA, U+25B6, U+25BA
→ ← ↑ ↓ ⇒arrowsU+2192, U+2190, U+2191, U+2193, U+21D2
★ ☆ ♥ ♦stars, heart, diamondU+2605, U+2606, U+2665, U+2666
§ ¶ † ‡ … – —section, pilcrow, daggers, ellipsis, dashesU+00A7, U+00B6, U+2020, U+2021, U+2026, U+2013, U+2014
© ® ™ ° ± × ÷copyright, registered, trademark, degree, plus-minus, multiply, divideU+00A9, U+00AE, U+2122, U+00B0, U+00B1, U+00D7, U+00F7

Conditional checkbox pattern:

Approved: {#IF IsApproved__c}☑{:else}☐{/IF}
Safety briefing: {#IF Safety_Done__c}☑{/IF}{^Safety_Done__c}☐{/Safety_Done__c}

What does not work in PDF: Wingdings/Webdings glyphs other than checkboxes (a neutral □ placeholder is printed), emoji (outside Arial Unicode MS coverage), and custom decorative fonts — generate DOCX for those.

Render the record's Classic Approval history — submission and every approval or rejection step — in the document: purchase orders listing every approver, NDAs stamped with each signatory's role and time, change forms with their audit trail.

Opt in by adding the standalone word Approvals to the Query Configuration (Name, Status__c, Amount__c, Approvals). The steps are exposed as a child collection:

{#Approvals}
{ActorName} ({ActorTitle}) — {StepStatus} — {CreatedDate:dd-MM-yyyy HH:mm}
Comments: {Comments}
{/Approvals}

Fields: ActorName, ActorTitle, ActorEmail, OriginalActorName (when reassigned), StepStatus (Started, Approved, Rejected, Removed, NoResponse), Comments, CreatedDate, ProcessStatus, SubmittedByName, SubmittedByTitle. Steps are chronological across resubmissions. Only steps that were acted on appear (pending work items are not included); Flow-based approval orchestration is a different object model and out of scope. Costs one extra query only when the token is present.

Not implemented#

  • Custom fonts in PDF — the engine supports Helvetica, Times, Courier and Arial Unicode MS only; @font-face is not supported. Generate DOCX for branded typefaces.
  • Comment or escape syntax — see the note at the top of this page.