Freemarker Template Engine Guide
Basic Syntax
Interpolation
Insert values into the output: ${ ... }
FTL Tags (Directives)
Predefined directives use #:
<#directiveName parameters>
</#directiveName>
User-defined directives use @:
<@mydirective parameters>...</@mydirective>
Include Files
<#include "header.html">
Comments
<#-- this is a comment -->
${user <#-- The name of user -->}
Computer Audience
For JavaScript fields, URLs with boolean or number values (not for human audience):
<a href="/shop/productdetails?id=${product.id?c}">Details...</a>
${someBoolean?c}
Variables
Reference: Freemarker assign directive
Declaration
<#assign seq = ["foo", "bar", "baz"]>
<#assign x++>
<#assign
seq = ["foo", "bar", "baz"]
x++
>
<#assign x="Hello ${user}!">
<#assign x>
<#list 1..3 as n>
${n} <@myMacro />
</#list>
</#assign>
<#-- Assign in other namespace -->
<#assign bgColor="red" in my>
Usage
${x}
Special Variables
Predefined variables use dot prefix:
.variable_name
Data Types
String
"Foo" or 'Foo' or "It's \"quoted\"" or 'It\'s "quoted"'
${r"${foo}"} <#-- raw data -->
Get character:
name[0]
Substring:
name[0..4] <#-- Inclusive end -->
name[0..<5] <#-- Exclusive end -->
name[0..*5] <#-- Length-based (lenient) -->
name[5..] <#-- Remove starting -->
Concatenation:
<#assign s = "Hello " + user + "!">
Methods:
${testString?upper_case?html}
${testSequence[1]?cap_first}
Number
Float to int conversion:
${1.999?int} <#-- => 1 -->
${-1.999?int} <#-- => -1 -->
Array
Declaration:
["foo", "bar", 123.45]
Get element:
products[5]
Sub Array:
products[20..29]
Concatenation:
users + ["guest"]
Methods:
${testSequence?size}
${testSequence?join(", ")}
Map
Declaration:
{"name":"green mouse", "price":150}
Get value:
user.name
user["name"]
${user[prop]}
Concatenation:
passwords + { "joe": "secret42" }
Iterate over keys:
<#list user?keys as prop>
${prop} = ${user.get(prop)}
</#list>
Range
0..9 <#-- 0 to 9 inclusive -->
0..<10 <#-- 0 to 9 (exclusive end) -->
0..!10 <#-- same as above -->
0.. <#-- infinite range starting from 0 -->
Type Casting
Freemarker is strict about type conversion:
${3 * "5"} <#-- WRONG! -->
${3 + "5"} <#-- => "35" (string concatenation) -->
Number to string:
123?string
Boolean to string:
${married?string("yes", "no")}
Operators
Arithmetic
(x * 1.5 + 10) / 2 - y % 100
Comparison
x == y, x != y, x < y, x > y, x >= y, x <= y
x lt y, x lte y, x gt y, x gte y
<#if (x > y)> <#-- can use parentheses -->
Note: <#if 1 == "1"> will cause an error (type mismatch).
Logical
|| <#-- Logical or -->
&& <#-- Logical and -->
! <#-- Logical not -->
!registered && (firstVisit || fromEurope)
Assignment
=, +=, -=, *=, /=, %=, ++, --
Control Structures
If Statement
Reference: Freemarker if directive
<#if x == 1>
x is 1
</#if>
<#if x == 1>
x is 1
<#else>
x is not 1
</#if>
<#if x == 1>
x is 1
<#elseif x == 2>
x is 2
<#elseif x == 3>
x is 3
</#if>
Loop (List)
Number repeat:
<#list 1..3 as n>
${n}
</#list>
Array loop:
<#list seq[1..3] as i>${i}</#list>
<#list array as item>
</#list>
Table example:
<table class="datatable">
<tr>
<th>Firstname</th>
<th>Lastname</th>
</tr>
<#list users as user>
<tr>
<td>${user.firstname}</td>
<td>${user.lastname}</td>
</tr>
</#list>
</table>
Exception Handling
Try-Recover
<#attempt>
Optional content: ${thisMayFails}
<#recover>
Ops! The optional content is not available.
</#attempt>
Missing Values (Default)
When expression has problems or is null:
${mouse!"No mouse."}
<#assign mouse="Jerry">
${mouse!"No mouse."} <#-- outputs "Jerry" -->
Syntax variations:
unsafe_expr!default_exprunsafe_expr!(empty default)(unsafe_expr)!default_expr(unsafe_expr)!
Missing Value Check
<#if mouse??>
Mouse found
<#else>
No mouse found
</#if>
Error Info
Reference: Freemarker special variables
${.error}
Functions
Declaration
<#function avg x y>
<#return (x + y) / 2>
</#function>
<#function avg nums...>
<#local sum = 0>
<#list nums as num>
<#local sum += num>
</#list>
<#if nums?size != 0>
<#return sum / nums?size>
</#if>
</#function>
Usage
${avg(10, 20)}
${avg(10, 20, 30, 40)}
${avg()!"N/A"}
Built-in Methods
If method has no parameters, () can be omitted. Use ? instead of .:
${testString?upper_case}
${testString?upper_case?html}
Macros
Declaration
<#macro macroName param1 param2>
...
</#macro>
Usage
<@macroName param1=val1 param2=val2/>
Merge string with variable:
<@messageInfoView index=index+"denom" obj=obj+".messageInfo" title="Message Information"/>
Default Value
<#macro defaultHead title="True">
Nested Content
<#macro macroName>
<#nested>
</#macro>
<@macroName>
<p>nested Code</p>
</@macroName>
Variable Arguments
<#macro defaultHead title="True Balance - MMS Console" extra...>
${extra["ngApp"]}
</#macro>
Import and Include
Import
<#import "/mylib.ftl" as my> <#-- my is namespace -->
Include
<#include "/footer/${company}.html">
Assign Variable to Namespace
<#assign bgColor="red" in my>
Comments