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_expr
  • unsafe_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>