Template examples.
A Templ8r template is just a Word document with merge tokens. Drop {{column}} placeholders where you want values, save, upload — done. This page walks every binding shape with a side-by-side of the Word excerpt and the rendered output.
01Token basics
A token is the column's logical name wrapped in double curly braces. Templ8r reads the value off the record and pastes it back in the same place — preserving every Word run, font, colour, and table cell around it.
{{name}} works but {{Order Name}} won't. Hover any column in the Templ8r mapper to see its logical name.02Finding the logical names
Tokens use the column's logical name — the lowercase, no-spaces, publisher-prefixed identifier that Dataverse stores under the hood (e.g. name, totalamount, new_projectref). Display names like Order Name won't resolve. Below are the four fastest ways a sysadmin or power-user can pull a clean schema list.
Power Apps maker portalThe default route. Open make.powerapps.com → pick your environment → Tables → choose the table → Columns. Each row shows Display name, Name (logical name) and Data type. Click any column to see the full schema name, required level, and — for lookups — the related table. For relationships, switch to the Relationships tab and grab the Schema name (used for navigation properties on N:1 and 1:N tokens).
Solution explorer (classic)For older orgs or audit work. Open the classic solution → expand your table → Fields and 1:N / N:1 / N:N Relationships. The Name column on each row is the schema name; the logical name is the same value lowercased. Useful when the modern UI hides system columns you actually need to bind to.
Dataverse Web API (bulk export)The fastest way to grab the whole schema at once. Hit the metadata endpoint with your browser or a REST client:
# All columns for a table — logical + schema + display name GET https://yourorg.crm.dynamics.com/api/data/v9.2/ EntityDefinitions(LogicalName='account')/Attributes ?$select=LogicalName,SchemaName,AttributeType # All N:1 lookups (gives you the navigation property name) GET https://yourorg.crm.dynamics.com/api/data/v9.2/ EntityDefinitions(LogicalName='account')/ManyToOneRelationships ?$select=ReferencingAttribute,ReferencedEntity,SchemaName, ReferencingEntityNavigationPropertyNamePaste in the browser tab where you're already signed into D365 — the response is JSON, copy-pasteable straight into a token list.
XrmToolBox / Level Up extensionPower-user shortcut. XrmToolBox (free) ships a Metadata Browser plugin that filters columns by table, type and prefix in one window — handy when you're standing up a template against an unfamiliar entity. The Level Up for Dynamics 365 Chrome extension adds an All fields button to any record form: it displays every column, its logical name, and the live value for that record — perfect for spot-checking a token before you paste it into Word.
customerid) and its navigation property (e.g. customerid_account) can have different names. For tokens you want the navigation property — that's what comes after the dot. Polymorphic customer-type lookups expose one nav property per target table (customerid_account, customerid_contact); pick the one matching the related record.new_, tobyd_, contoso_, etc. The prefix is part of the logical name and case-sensitive in the URL but not in tokens. If a column doesn't resolve, double-check the prefix matches the publisher of the solution that owns the table.03Lookups (N:1)
Reach into a related record with dot syntax. Reference the relationship's navigation property, then the column on the target entity. Lookup-type columns (Lookup, Owner, Customer) are auto-translated to their _col_value form — you write the friendly name, Templ8r handles the protocol.
Token shape: {{relationshipName.column}} — where relationshipName is the schema name of the lookup column or its navigation property (e.g. customerid_account or primarycontactid).
04Multi-hop chains
Chain dots to walk N:1 lookups to any depth. Templ8r compiles the whole binding set into a single nested $expand with column-scoped $select at every hop — one record fetch, full document tree.
Compiled query (peek under the hood):
// Templ8r builds this for you, given the four bindings above GET /api/data/v9.2/salesorders({id})? $select=name &$expand=customerid_account( $select=name; $expand=parentaccountid( $select=name; $expand=territoryid( $select=name; $expand=managerid($select=fullname) ) ) )
customerid column on a sales order, which can target either Account or Contact) work two ways:- Bare attribute (recommended). Bind to
customeridand write{{customer.name}}— Templ8r fans out into one$expandfragment per target type in a single OData call and uses whichever variant the record actually points at. Same template works on records pointing at either Account or Contact. - Typed variant (legacy, still works). Bind explicitly to
customerid_accountorcustomerid_contactwhen you need target-specific fields. The visual mapper picks the right one if you tell it which target the column points at.
05Choice + status fields
Option-set, status, and statecode fields render as their friendly label by default — Dataverse provides a FormattedValue annotation alongside the raw integer, and Templ8r prefers the friendly form so your document reads naturally without any extra mapping.
@OData.Community.Display.V1.FormattedValue annotation Dataverse returns alongside the raw integer. That’s usually what you want on a customer-facing document. If you specifically need the raw integer in the output (e.g. you're generating a CSV for a downstream system), bind a separate token to a calculated column that forces the integer form — or talk to us about a custom extension.06Repeating rows (1:N)
For order lines, invoice items, contacts on an account — anything that's a 1:N collection. Place tokens inside a Word table row and Templ8r repeats the row for every related record. Header and footer rows pass through untouched.
Token shape: {{collectionName.column}} — where collectionName is the 1:N relationship name (e.g. order_details, aliased to lines in the mapper for readability).
07Custom entities
The token grammar doesn't care whether the entity is shipped by Microsoft or by your custom solution. Tokens against publisher-prefixed entities (cr1ad_project, acme_workorder, new_assessment) bind exactly the same way. The prefix just becomes part of the column name.
Multi-hop, polymorphic, formatted-value, repeating rows, conditionals, aggregations — everything else on this page works identically against custom entities. The only difference is the prefix on the column / relationship / child-entity logical names.
{parent}_{child} by default — e.g. cr1ad_project_cr1ad_projecttask. Field tokens nested inside use the full relationship name as the prefix: {{cr1ad_project_cr1ad_projecttask.cr1ad_completionpercent}}. Walk relationships in the visual mapper to confirm the exact schema names — they vary per solution.cr1ad_ custom entities. Swap the prefix for whatever your solution uses (acme_, new_, your_) and every other token shape stays the same.08Conditional sections
Wrap a paragraph, table row, or whole document section in {{#if x}} ... {{/if}} guards. The merge engine drops the entire block when the column is empty, null, false, or zero — so empty risk notes, never-revised quotes, and accounts without follow-up actions just don’t print at all.
All three control-flow shapes:
{{#if x}} ... {{/if}}Render the block when x is truthy (non-empty / non-null / non-zero / non-false). Choice columns are truthy whenever they have any value selected.
{{#unless x}} ... {{/unless}}Inverse — render only when x is falsy. Useful for “outstanding” / “not yet completed” messaging.
{{#if x}} ... {{else}} ... {{/if}}Two-branch fallback. Renders the second block when x is falsy. The two halves can carry completely different copy — e.g. paid vs. overdue messaging on an invoice.
Comparison operators (V2):
You can now compare a column against a literal directly inside the guard. Comparisons use keyword operators (not = or <) so Word’s autocorrect can’t mangle them. String comparisons are case-insensitive.
{{#if account.name eq "Cloud2020"}}Render when the column equals the literal. Use ne for the inverse.
{{#if order.total gt 1000}}Numeric comparisons: gt, lt, gte, lte. Both sides auto-coerce — numeric if both parse as numbers, then date in the document’s culture, then string.
{{#if contact.emailaddress1 endswith "@cloud2020.co.uk"}}Substring matching: contains, startswith, endswith — all case-insensitive.
{{#if account.country in "UK","IE","FR"}}List membership. Comma-separated string or numeric literals; quotes optional for numbers.
{{#if order.total gt 1000 and account.country eq "UK"}}Combine guards with and / or. Strict left-to-right; no parentheses. Nest {{#if}} blocks for grouped logic.
Multi-branch with {{else if}}:
{{#if order.total gt 1000}}
Big-order discount applies.
{{else if order.total gt 500}}
Mid-tier discount applies.
{{else}}
No discount.
{{/if}}"double" and 'single' quotes. Word’s smart-quote autocorrect (curly “ ”) is normalised automatically — paste from anywhere.{{#if x eq "Y"}} is false when x isn’t in the record. The bare {{#if x}} truthy form is preserved exactly — templates that worked before still work.{{#if}}, {{else if}}, {{else}} and {{/if}} must occupy a paragraph of its own. Inline conditionals inside a sentence and conditionals split between a body paragraph and a table cell are not supported in V1 — the engine will throw a template error rather than silently miss the close.09Aggregation functions
Roll up values across a 1:N collection without copy-pasting every line item into a Word formula field. Functions read the same merge context the repeating-row pass uses, so they cost zero extra Dataverse round-trips.
Five built-ins:
{{count(coll)}}Number of rows in the collection. No field argument needed.
{{sum(coll.field)}}Sum of field across every row. Non-numeric values skipped.
{{avg(coll.field)}}Arithmetic mean. Empty collection yields 0.
{{min(coll.field)}}Smallest numeric value across the collection.
{{max(coll.field)}}Largest numeric value across the collection.
010Number, date, currency
Append a format hint after a colon. Hints pass through to the same .NET formatting engine OpenXML merges use under the hood, so anything ICU/.NET supports works.
{{totalamount:C}}Currency in tenant culture. £8,340.00 in en-GB, $8,340.00 in en-US. Use :C2 to force two decimals.
{{totalamount:N0}}Number with thousands separator, no decimals. 8,340.
{{createdon:d}}Short date in tenant culture. 06/05/2026 in en-GB, 5/6/2026 in en-US.
{{createdon:dd MMMM yyyy}}Custom date format. 06 May 2026.
{{createdon:HH:mm}}Time only, 24-hour. 14:32.
{{discount:P1}}Percentage with one decimal. 12.5% for a stored value of 0.125.
- Colon-direct (D365 / Word native):
{{totalamount:C}},{{createdon:dd MMM yyyy}}. The spec after:is passed straight to.ToString(spec, culture)— any standard .NET format string works, including ones with internal colons likeHH:mm. Currency, percentage, short/long dates all follow tenant culture. - Pipe form (named formatters with options):
{{totalamount | currency:GBP}},{{createdon | date:short}}. Use this when you need to override the tenant culture — e.g. forceGBP,USD, orEURregardless of the running culture. Named formatters:currency,number,date,upper,lower.
011What gets fetched
Only the columns referenced in the template's bindings hit Dataverse. Templ8r builds an entity-scoped $select from your bindings, plus nested $expands for any relationship paths. No SELECT *, no over-fetching, no surprise rows in your audit log.
Lookup → _col_value translationLookup, Owner and Customer columns are auto-rewritten to _col_value in the $select. You write {{ownerid.fullname}}, Templ8r requests _ownerid_value and walks the FormattedValue annotation to render Jamie Reid.
FormattedValue resolutionChoice fields, money columns and dates carry an @OData.Community.Display.V1.FormattedValue annotation. Templ8r prefers it over the raw integer/decimal so your document reads as a human would — without you mapping option-set keys to labels.
Token-level format hintsFormat hints are applied after Dataverse responds, in our merge engine. They never alter the $select — so adding or removing :C2 doesn't change what gets fetched.
012Full sales-order example
Putting it all together. Below is the entire Word excerpt for a branded sales order — header, customer block, line items, totals — and what it renders for the demo Acme record.