How to write a document template

What is a document template?

A document template is a typical document created with any text editor and saved as DOCX, DOC, PDF or ODT format. A template can contain fields and/or functions.

Our product uses those fields to insert data and generate documents that can be downloaded in DOCX or PDF format.

We recommend users to create templates using Microsoft Word, Office 365 or Google Docs and to save their templates as DOCX when using Word or Office 365 and as ODT when using Google Docs.

Simple fields

We support the inclusion of elements that simply match an element of data filled in by the user in the User Interface. Wherever an element occurs, our product substitutes the actual data value in the document. The inserted data inherits all the typesetting characteristics that are applied to the field, such as font and paragraph style.

The syntax for a simple field is:



We discourage the use of hyphen - in field names. If you choose to use one, you will need to surround the field name with brackets ([ ]) when using it into an expression, such as in Conditions, Date format, or Number format. For example, <<[my-field-with-hyphens]>>

By default, the start of a field is annotated by << and the end of a field by >>. So to create a field that will be replaced by the value of the field “firstname” for the employee, you would simply type <<firstname>> into the document.

To be as unobtrusive as possible to the text of a template, our tool is strict about identifying fields and ignores invalid mark-up, which is interpreted as plain text. For example, <<firstname> is ignored and left as plain text because a closing > character is missing. A single space between the << and the name or the name and >> is allowed, but more also result in the field not being recognized. The following table shows the typical types of error that result in a field not being recognized.








Missing trailing >



Missing leading <

<< firstname  >>


2 spaces before trailing >>

< <firstname >>


Space after leading <

Optional fields

Optional fields operate like the fields described earlier, except if there is no data for the value the entire paragraph containing the field is removed. Optional fields are specified with the prefix op: , for example:


Optional fields are useful for condensing output (not leaving behind blank lines) when populating data. Consider a typical address block:

<<city>>, <<country>>

If you fill in no value for address2, the output using the above sequence looks as follows:

53 Avenue Mirabeau

Paris, France

The blank line in the middle of the output above is likely to be undesirable and so using an optional field:

<<city>>, <<country>>

Results in the required output:

53 Avenue Mirabeau
Paris, France

Optional fields are also useful for removing paragraphs from numbered or bullet lists. Consider:

I have one <<item1>>
I have one <<item2>>
I have one <<item3>>

With data item2 = orange and item3 = banana, this results in:

I have one
I have one orange
I have one banana

Clearly item #1 above is incomplete because there is no data for it. Changing to optional paragraph fields resolves this:

I have one <<op:item1>>
I have one <<op:item2>>
I have one <<op:item3>>

With data item2 = orange and item3 = banana, this results in:

I have one orange
I have one banana


Optional Fields strip the entire line or paragraph. Any additional content is also removed.

Known fields

When using Smart Document Generation through our User Interface, you have the possibility to add predefined fields in your document template. These fields are divided into two types, standard fields which are known by our database without configuration on your side, and custom fields that you can create when configuring UKG HR Service Delivery platform.

If you insert a field in your template with the same name as one of these fields then, it is automatically detected.

Please note that the variable is replaced by the expected value only if the corresponding information is present in the employee folder.

Standard fields

  • <<firstname>>

  • <<lastname>>

  • <<external_id>>

  • <<email>>

  • <<status>

  • <<terminated>>

  • <<middlename>>

  • <<maidenname>>

  • <<language>>

  • <<birth_date>>

  • <<address1>>

  • <<address2>>

  • <<address3>>

  • <<zip_code>>

  • <<city>>

  • <<country>>

  • <<state>>

  • <<mobile_phone_number>>

  • <<starting_date>>

  • <<registration_references.employee_number>>

  • <<>> (Note that this value is true or false depending on the selected employee number)

  • <<registration_references.departure_date>>

  • <<registration_references.organization_id>>

  • <<registration_references.organization_name>>

  • <<registration_references.organization_address1>>

  • <<registration_references.organization_address2>>

  • <<registration_references.organization_zip_code>>

  • <<registration_references.organization_city>>

  • <<registration_references.organization_country>>

  • <<hr_firstname>>

  • <<hr_lastname>>

  • <<hr_middlename>>

  • <<hr_email>>

  • <<hr_external_id>>

  • <<hr_language>>

  • <<hr_mobile_phone_number>>


You want to display the first name, middle name, last name and birth date of an employee, by adding these four fields to your document template: <<firstname>> <<lastname>> <<middlename>> <<birth_date>>, and if the information is present for the employee, the values are automatically filled when generating the document.

Custom fields

The syntax to insert a custom field in a template is the following:



  • The code of the custom field that you have created is salary_amount, then the field name is <<custom_fields_salary_amount>>.

  • The code of the custom field that you have created is assignment-category then the field name is <<custom_fields_assignment-category>>.

Content blocks

Content blocks are parts of templates to be inserted into other templates. They are useful when you need to insert recurring content into several of your templates, such as logos and legal mentions.

To insert a content block in your template you need to add this field:



Here my_block is the name of the reference to the content block but you can actually call it whatever you want. When you upload your template in the User Interface, the refLookup syntax is detected and you are prompted to link this reference to the content blocks of your choice. Therefore, it is easier to create the content blocks before you create your templates.


Note that inserting a content block within another content block is not supported.
Therefore, a content block file should not contain any refLookup statement.


No digits are allowed directly after the refLookup statement.
<<refLookup:myfield007>>: Correct
<<refLookup:myfield>>: Correct
<<refLookup:my007field>>: Correct
<<refLookup:007myfield>>: Incorrect


Conditional content is populated in the final document depending on the data filled in by the HR User in the User Interface. If, and only if, the specified condition is met, the content within the matching conditional section is rendered in the document.

Each conditional section is defined using a pair of fields: one to start the section and one to end it. The general syntax is:

The text and elements of the conditional section.


cs stands for “Conditional section” and es for “End section”.

To provide else and else if options in a conditional section you can use the following syntax:


The conditions can use expressions, variables and range specifiers, or any of the operators in this list:



( / )

Open / Close parenthesis (for math operations)

= or ==

Equals (for numbers and strings)


Not equals (for numbers and strings)


Addition (for numbers and strings)










Less than (for numbers and strings)


Less than or equals (for numbers and strings)


Greater than (for numbers and strings)


Greater than or equals (for numbers and strings)






NOT (for boolean expressions)

The conditional start and end tags are removed from the resulting document. If each tag is on a line by itself, the entire line is removed and the result is cleaner, this is why we recommend using those tags on dedicated lines when possible.

You can find an example template with conditions in the Example templates section.


Comments are sections of the templates that are ignored by document processing and never appear in the generated document. They are useful for:

  • Creating permanent notes in the template that are helpful to template authors and maintainers.

  • Disabling sections of templates temporarily to assist with development and maintenance.

Due to the two distinct requirements for comments above, there are times when you may need to comment out a section of a template which itself contains other comments. For example, you are temporarily disabling a section of the template which happens to contain comments that are permanent. To support this, we provide several distinct comment markup delimiters as shown by the following table. Different delimiters may be used to nest comments inside other comments.

Start delimiter

End delimiter





As an example, a single comment may look as follows:

<</* The following section displays the person details if they exist */>>


And nested comments may looks as follows:

<<## This entire section is disabled
<</* This displays the salary of my employee */>>


Dates can be inserted in the document template and formatted in the desired output format.


For this to work correctly, once the template is uploaded, the field must be edited and its type set to Date.

The syntax for inserting a date in the template is:


Date formats

By default, if no desired output is specified the default output format is dd MMM yyyy (eg. 25 Nov 2021). If you want to format a date on a specific format, the syntax is:

<<{dateFormat(myDate, 'output-format')}>>

If you want to format a date according to the template language, the syntax is:

<<{dateFormat(my_date, 'EEEE MMMM yyyy', null, templateLocale)}>>


If the date selected for myDate in the date picker was 25 November 2021, the following table shows what different formats would produce:






25 Nov 2021



<<{dateFormat(myDate,'MMMM dd, yyyy')}>>

November 25, 2021

<<{dateFormat(myDate,'EE, dd MMMM yyyy')}>>

Thu, 25 November 2021

<<{dateFormat(myDate, 'yyyy')}>>



You can directly get the value of today’s date by using the variable $nowUTC.
Example: <<{dateFormat($nowUTC,'dd/MM/yyyy')}>>

Operations on dates

To calculate the dates in your document (for example the end of a trial period or a contract end date), the syntax is:

<<{dateAdd(Date, amount, units)}>>


  • Date is name of the date field you want to add a time period to.

  • amount is the number of days, months, years to add or remove.

  • units is day, month or year (singular or plural).

For example:

The expiration date of a trial period of 3 months from the starting date:

<<{dateAdd(starting_date, 3, 'months')}>> adds 3 months to the starting date.

You can also calculate the difference between two dates in a given unit using the following syntax:

dateDiff(date1, date2, unit)


  • date1 is the starting date.

  • date2 is the ending date.

  • unit is day, month or year (singular or plural).

For example:

<<{dateDiff('10 Jul 2020', '18 Jul 2020', 'days')}>> results in 8

Number format

If you want to format a number on a specific format/pattern, the syntax is:

<<{numFormat(number, '### ###,##', 'FR', 'false')}>>

where ### ###,## represents the pattern to display, and FR is the optional locale that specifies the input format of number.


<<{numFormat("1234,5", '#.###,#', 'DE')}>> displays 1.234,5
(Input value is in the German format and so is the output value)

<<{numFormat("1234.5", '#,###.#', 'US')}>> displays 1,234.5
(Input value is in the US format and so is the output value)

You can also add the ¤ character to the pattern to display the currency of the chosen locale:
<<{numFormat("1234,5", '#.###,00¤', 'DE')}>> displays 1.234,50€


To write the pattern, non-breakable space character must be inserted (CTRL + Shift + Space on MS Word and LibreOffice) instead of a simple space.


Calculations can be performed in the generated document according the data filled in by the user in the User Interface. The result of a calculation can either be simply displayed in the generated document or used in conditions.

The syntax to create a calculation to be displayed in the generated document is:

<<{number1 + number2}>>

where + can be replaced by any of the operators on this list:



( / )

Open / Close parenthesis (for math operations)


Addition (for numbers and strings)









Here is the syntax to store a calculation in a variable $myResult , to be used later on in a condition:

<<$myResult={number1 + number2}>>


The text and elements of the conditional section.

Other functions

Several other functions can be used in a document template:




Determines if the given element is null or empty.


- key = the data value

Example: <<{isBlank(name)}>> checks the value of name and returns true if it is null or empty, otherwise false.

This can be useful for conditional sections:
There is no address


Uses a default value if the given element is null or empty.

ifBlank(key, default)

- key = the data value
- default = the value to use if key is blank

Example: <<{ifBlank(name, 'Not Specified')}>>

looks up name in the data and returns Not Specified if it is null or empty.


Maps one value to another.

map(key, test1, replace1 [,test2, replace2 ...] [,default])

- key = the data value
- test1 = the first value to compare with the key
- replace1 = the value to use if test1 matches the key
- test2 = the second value to compare with the key
- replace2 = the value to use if test2 matches the key
(And so on)
- default = the value to use if no matches are made

Example: <<{map(gender, 'M', 'Male', 'F', 'Female', 'Other')}>>

looks up gender in the data and if it equals M the value Male is displayed.

You can also use the mapi function which is the same but case insensitive on the key and values.


Replaces characters in the source string with new characters.

replace(string, oldChar, newChar)

string = the string
oldChar = the character to find in the string
newChar = the character to use in place of the oldChar

Example: <<{replace(customerVIN, 'o', '0')}>>

If the value of customerVIN is JHMAB5227EC8oo65o,
then the replace function replaces all the o characters to the number 0.
The result is JHMAB5227EC800650.


Replaces strings in the source string with the new string.

replaceStr(string, searchFor, replaceWith [, ignoreCase])

- string = the string
- searchFor = the characters to find in the string
- newChar = the characters to use in place of searchFor
- ignoreCase = true | false to ignore the case when searching. Defaults to false (ie case-sensitive)

Example: <<{replaceStr(address, 'street', 'St.', true)}>>

If address = Matheson street, the function returns Matheson St.

Note that this function returns every occurrence of the found string.
If you want to replace only the first occurrence, use the replaceFirst function instead.


Replaces all double quote characters in the given string with single quotes.
All forms of double quotes are replaced. This is handy since the templates use single quotes for delimiters.


- string = the string in which to replace double quotes

Example: <<{squote('This is Amy"s.')}>> returns This is Amy's.


Splits a string into parts that can be displayed separately.

split(string, splitChar, index)

- string = the string to split
- splitChar = the character to use as a delimiter
- index = once split into parts, index identifies the part to be used – counting from 0

Examples: <<{split('John|Mathews|47|Approved' , '|' , 1)}>>
returns Mathews

<<{split(cityStateZIPCountry , ';' , 3)}>>
if cityStateZIPCountry = Charleston;West Virginia;29402;United States
then the function returns United States.


Displays a subsection of a string given starting and finishing indexes.

substring(string, start, finish)

- string = the string
- strat = the position in the string that becomes the first character. Indexing starts at 0.
- finish = the position in the string that marks where to cut the string; the character before the cut makes it into the substring, the ending character doesn’t.

Examples: <<{substring('0123456' , 2 , 5)}>> returns 234

<<{substring(LatLong , 0 , 6)}>>
with LatLong = 31.9088983S115.8049265E returns 31.908


Removes leading and trailing spaces from a string.


- string = the string

Example: <<{trim(productID)}>>

where productID = "   12CVCV123-454     "
returns 12CVCV123-454


Returns the character at the requested position in the source string.

charAt(string, position)

- string = the string to lookup the character in
- position = the position of the required character, starting from 0 for the first position

Examples: <<{charAt('abcdefg', 3)}>> returns the character d.

<<{charAt(myNumber, 6)}>> looks up myNumber in the data.
If myNumber = ID474-K234 then the function returns K.


Checks to see if a string ends with a given string.

endsWith(mainString, subString)

- mainString = the string to check
- subString = the string to look for at the end of mainString

Example: <<{endsWith('I am a potato', 'tato')}>> returns true.

Useful when creating a conditional section. Here, the serialNum field is only displayed if it ends with ZZZ:
<<cs_{endsWith(serialNum, 'ZZZ')}>>
The startsWith function does the same thing for the beginning of a string.


Compares two strings, regardless of case.

equalsIgnoreCase(string1, string2)

- string1 = the first string
- string2 = the second to compare to the first string

Example: <<{equalsIgnoreCase ('i am Bob', 'I am bob')}>> returns true.


Returns the length of a string.


- string = the string which length must be checked

Example: <<{length('Bob')}>> returns the number 3.0.

Can be used when creating a conditional section. Here, the section is only displayed if refNo is set:
<<cs_{length(refNo) > 0}>>
Ref Num : <<refNo>>
However, we recommend using the isBlank function for this specific use case.


Changes the string so that the first character of each word is a capital letter.


- string = the string to convert

Example: <<{titleCase ('bob mathews')}>>
returns Bob Mathews.

<<{titleCase (firstname+ ' ' + lastname)}>>

with firstname = bob and lastname = MATHEWS
also returns Bob Mathews.


Adjusts the given string converting it to sentence case.


- string = the string to convert

Example: <<{toSentence('a little. ditty')}>>
returns A little. Ditty.


Returns the string using all lower case characters.


- string = the string to convert

Example: <<{toLowerCase('Bob Mathews')}>>
returns bob mathews.

The toUpperCase function does the same thing with upper case characters.


Returns the starting index of one string inside another.
Note that the first index is 0.

indexOf(string, find [, startIdx])

- string = the string to scan
- find = the string to find
- startIdx = an optional search starting index

Example: <<{indexOf('Bob Mathews', 'Mat')}>>
returns 4.0.


Converts a given number to a letter in the following sequence:
a, b, c, … z, aa, bb, cc, … zz, aaa, bbb, etc.


- key = the data value

Example: <<{toAlpha(index)}>>

when index = 3, the function returns c.
when index = 28, the function returns bb.

The function toAlpha2 does the same thing with the sequence:
a, b, c, … z, aa, ab, ac … az, ba, bb, etc.


Converts a given number to a roman numeral:


- key = the data value

Example: <<{toRoman(index)}>>

when index = 2, the function returns ii.
when index = 29, the function returns xxix.

min and max

Respectively returns the smaller or larger of two numbers.

min(number1, number2) and max(number1, number2)

<<{min(53.5,23.1)}>> returns 23.1
<<{max(53.5,23.1)}>> returns 53.5

floor and ceil

Respectively returns the next smaller or larger whole number.

floor(number) and ceil(number)

<<{floor(153.57)}>> returns 153.0
<<{ceil(153.57)}>> returns 154.0


Returns the absolute value of a number.


<<{abs(-153.57)}>> returns 153.57
<<{abs(temp)}>> with temp = -273.15, returns 273.15


Returns the power of two numbers.

pow(number1, number2)

Example: <<{pow(7,2)}>> returns 49.0


Returns the square root of a number.


Example: <<{sqrt(81.0)}>> returns 9.0


Rounds the number to the specified number of places.

round(number [, places])

- number = the number to round
- places = the number of decimal places required (defaults to zero)

<<{round(153.75)}>> returns 154
<<{round(153.73455,2)}>> returns 153.73


Returns a random number between 0 and 1.


Example: <<{round(random()*100)}>> returns a random number between 0 and 100.

Example templates

You can download these templates to avoid starting from scratch: