Document Templating
Getting Started
Realpad CRM can generate documents (contracts, invoices, protocols, etc.) from Word templates.
You create a .docx file with special placeholder markers, upload it to the system, and the CRM fills in the real data automatically.
The system uses XDocReport with the Apache Velocity template engine under the hood. You don't need to understand these libraries in depth β this guide covers everything you need to create templates.
Document types & their placeholders documentation
Deal documents: reservation, purchase, ... contracts and everything that needs to be created from the context of a single Deal. These placeholders are also available in other document types related to / extending the Deal context.
Invoices: documents created from a single Payment on a Deal.
Step-by-Step: Creating a Template
Insert Placeholders Using Merge Fields (Recommended)
The recommended way to insert placeholders is through Word Merge Fields. This prevents a common issue where Word silently splits your placeholder text across internal formatting runs, breaking it (see Common Pitfalls for details).
How to insert a Merge Field:
Place your cursor where you want the placeholder.
Press
Ctrl+F9(Windows) orCmd+F9(Mac) to insert a field β you will see gray braces{ }.Inside the field braces, type:
MERGEFIELD $placeholderName(for example:MERGEFIELD $dealId), orMERGEFIELD $placeholderName \* MERGEFORMAT.Press
Alt+F9(orShift+F9on Mac) to update the field display β it will now show the placeholder name as a preview.
Important: Do NOT type the braces { } manually β they must be inserted by Word using Ctrl+F9 / Cmd+F9. Manually typed curly braces look similar but are not the same thing and will not work.
Placeholder Syntax
Simple Placeholders
A simple placeholder starts with $ followed by the name:
Accessing Properties of Objects
Inside a loop, each item is an object with multiple properties. Use a dot . to access them:
This also works for other objects β for example, units inside a #foreach($unit in $units) loop:
Loops (Repeating Content)
To repeat a section for each item in a list, use #foreach ... #end:
This generates one block of text for each Unit on the Deal. Everything between #foreach and #end is repeated.
Loops in tables: A common pattern is to put the #foreach and #end in the first column of a table row. The entire row gets repeated for each item:
#foreach($unit in $units) $foreach.count
$unit.internalId
$unit.type
$unit.finalPriceVat #end
Note: $foreach.count is a built-in Velocity variable that gives you the current iteration number (1, 2, 3, ...). You can also use $foreach.index for zero-based numbering (0, 1, 2, ...) and $foreach.hasNext to check if there are more items.
Conditional Content
To show content only when a condition is true, use #if ... #end:
You can also use #else and #elseif:
Checking if a value exists: Many placeholders come with a boolean companion (prefixed with is). Use it to check before displaying:
You can also use the $Utils.isSet() helper to check any value:
Comparing values: You can compare numbers and strings inside #if:
Note: To compare numbers, use .getAsNumber() first β otherwise you'd be comparing the formatted text, not the numeric value.
Variables
You can define temporary variables with #set to store computed values or simplify repeated expressions:
Nested Loops
Loops can be nested. For example, to list defects for each room during an inspection:
Velocity Reference
The template engine is Apache Velocity. The most commonly used features are covered in this guide, but for the full syntax reference, see the Velocity User Guide. The key directives are #if/#elseif/#else/#end, #foreach/#end, and #set.
Data Types and Methods
Every placeholder has a data type that determines how it is displayed and what methods you can call on it.
String
Plain text values. Rendered as-is in the document.
Cyrillic Transliteration (RS developers only): For Serbian (RS) developers, String placeholders support .toCyrillic(). This is typically used inside a #foreach loop:
Important: .toCyrillic() is only available for RS developers. For all other developers, String values are plain text and calling .toCyrillic() will output the method call literally. Templates that use .toCyrillic() should only be used by RS developers.
DateWrapper
Formatted dates that respect the user's locale settings.
Available methods:
.format("pattern")
Custom date format using Joda-Time patterns
$createdOn.format("dd. MMMM yyyy") β "23. December 2025"
.addDays(n)
Add days to the date
$createdOn.addDays(14) β date + 14 days
.addWeeks(n)
Add weeks to the date
$createdOn.addWeeks(2) β date + 2 weeks
.addMonths(n)
Add months to the date
$createdOn.addMonths(3) β date + 3 months
.isSet()
Check if date has a value
#if($createdOn.isSet()) ... #end
Common date format patterns:
dd.MM.yyyy
23.12.2025
d. MMMM yyyy
23. December 2025
dd/MM/yyyy
23/12/2025
yyyy-MM-dd
2025-12-23
EEEE, d. MMMM yyyy
Tuesday, 23. December 2025
Note: The month and day names (e.g. "December", "Tuesday") are automatically localized to the user's language.
NumberWrapper
Formatted numbers that respect the user's locale (decimal separator, thousands grouping, currency symbol).
Available methods:
.add(number)
Add to the value
$dealPriceVAT.add($otherPrice)
.sub(number)
Subtract from the value
$dealPriceVAT.sub($discount)
.mul(number)
Multiply the value
$unit.area.mul(2)
.div(number)
Divide the value
$dealPriceVAT.div(2)
.round()
Round to whole number
$dealPriceVAT.round()
.roundFloor()
Round down to whole number
$dealPriceVAT.roundFloor()
.toInt()
Convert to integer (truncate)
$unit.area.toInt()
.strip()
Remove trailing zeros from decimals
$dealPriceVAT.strip() β "1 250 000 CZK"
.keep()
Always show decimal places
$dealPriceVAT.keep() β "1 250 000,00 CZK"
.dash()
Show dashes instead of trailing zeros
$dealPriceVAT.dash() β "1 250 000,β CZK"
.cz()
Number written in Czech words
$dealPriceVAT.cz()
.sk()
Number written in Slovak words
$dealPriceVAT.sk()
.isSet()
Check if number has a value
#if($dealPriceVAT.isSet()) ... #end
.getAsNumber()
Raw number for comparisons
#if($dealPriceVAT.getAsNumber() > 0) ... #end
Tip: Many number placeholders also come with pre-computed _cz and _sk word variants as separate placeholders (e.g. $dealPriceVAT_cz). These are equivalent to calling .cz() on the base placeholder.
Arithmetic: Methods accept both other placeholders and plain numbers, and can be chained:
Boolean
True/false values, used in #if conditions:
$Utils Helper Object
A utility object (DealDocumentUtils) available in all document templates β Deal Documents, Invoices, Reclamation Protocols, and others.
$Utils.isSet(value)
Check if any value is set (works with all types)
#if($Utils.isSet($mortgageAmount))
$Utils.sum(n1, n2, ...)
Sum multiple NumberWrapper values
$Utils.sum($unit.area, $unit.areaBalcony)
$Utils.wrap(number)
Create a NumberWrapper from a raw long or double
$Utils.wrap(100)
$Utils.isSet() returns false for null values, empty collections, numbers/dates that have no value, and missing images. It is the most reliable way to check whether a placeholder has data.
Usage example:
Method Chaining
Both DateWrapper and NumberWrapper methods return new wrapper objects, so you can chain multiple operations:
Embedding Images
Some placeholders provide images (for example, Unit floor plans or Inspection defect photos). These work similarly to text placeholders but require a different insertion method in the Word template β you use bookmarks instead of Merge Fields or typed text.
How to add an image placeholder
Insert a placeholder image into your Word document (any small image will do β it serves as a sizing reference)
Select the image
Go to Insert β Bookmark
In the Bookmark name field, type the image placeholder name β but replace dots with underscores (for example, for the placeholder
$unit.plan, typeunit_plan)Click Add to create the bookmark
Resize the placeholder image to the approximate dimensions you want the real image to have in the generated document β the generated image will match this size
When the document is generated, the bookmarked placeholder image is replaced with the actual image from the CRM. If no image is available for a given placeholder, the placeholder image is removed from the output (it will not appear as a broken image).
Important: Bookmark names cannot contain dots (.) or dollar signs ($). Always replace dots with underscores: $unit.plan β bookmark name unit_plan, $defect.image β bookmark name defect_image.
Image placeholders in loops
Images work inside #foreach loops too. For example, to show a photo for each inspection defect, you would insert a placeholder image inside the loop body and bookmark it with the image field name (using underscores). The image is then replaced for each iteration of the loop.
Available image placeholders
Image placeholders are listed in the placeholder reference for each document type. Look for placeholders marked as Image type. Common examples include floor plan images on Units and defect attachment photos on Inspection documents.
Common Pitfalls
A best practice to investigate a problematic template, especially if it is based on a working one, is to split it approximately in half, remove one half and:
If the smaller part works, the problem was in the other part.
If the smaller part doesn't work, the problem is in it.
In both cases, split down the problematic part further, until you discover the problematic placeholder or control structure.
1. Broken Placeholders (Word Formatting Runs)
The most common issue. Word internally splits text into "runs" based on formatting. If you type $dealId and then go back to fix a typo or apply formatting, Word may store it internally as $deal + Id in separate runs. The template engine then cannot recognize the placeholder.
How to fix:
Select the entire placeholder text, cut it, and paste it back using Paste as Plain Text (
Ctrl+Shift+V)Or use Merge Fields instead (see Step 2 β Insert Placeholders Using Merge Fields)
Or type the placeholder in one go without going back to edit it
How to diagnose: If a placeholder appears literally in the generated document (e.g., you see $dealId instead of a number), this is almost certainly the cause.
2. Missing `$` Sign
Every placeholder must start with $. If you write dealId instead of $dealId, it will appear as literal text.
3. Unclosed `#foreach` or `#if` Blocks
Every #foreach needs a matching #end, and every #if needs a matching #end. A missing #end will cause the template generation to fail.
Example:
WRONG:
CORRECT:
4. Using a Placeholder That Doesn't Exist
If you use a placeholder name that the system doesn't recognize, it will appear literally in the generated document (e.g., $nonExistentField). Double-check the name against the placeholder reference for your document type.
5. Placeholder from Wrong Document Type
Each document type has its own set of available placeholders. For example, Invoice-specific placeholders like $paymentType are not available in Reclamation documents. Check which document type you are creating the template for.
6. Loops Inside Tables
When using #foreach in a table, the #foreach and #end tags must be in the same table row. Placing #foreach in one row and #end in another will cause unexpected results.
7. Whitespace and Newlines
#foreach, #if, and #end directives take up a line in the output. If you don't want extra blank lines, place them on the same line as adjacent content, or put them at the start/end of a paragraph.
Quick Example
Here is a minimal Deal Document template:
A more complete example using the $customers list:
Last updated