Skip to main content

Velocity directives

Velocity directives are special instructions that control how your template is processed. They always begin with a # character. This article covers the core Velocity directives you are most likely to use when building Cascade CMS Formats, with practical examples and common pitfalls. Cascade-specific query directives are covered separately in the Query Tool Directives article.

Directive What it does
#setAssign a value to a variable.
#if / #elseif / #elseRender content conditionally.
#foreachIterate over a collection.
#breakExit the current #foreach loop early.
#stopHalt all template processing immediately.
#macroDefine a reusable, parameterized block of code.
#importPull macros and shared code from another Format.
#evaluateExecute a Velocity string built at runtime.
#defineCapture a block of template content for later reuse.
#parseInclude and evaluate another Format inline.

#set

The #set directive assigns a value to a variable. Once set, the variable is available for the remainder of the template (or until overwritten).

Basic syntax

Velocity
#set ($variable = "value")

Supported value types

Velocity
## Strings
#set ($name = "Engineering")

## Numbers
#set ($count = 42)

## Booleans
#set ($isActive = true)

## Lists (ArrayLists)
#set ($colors = ["red", "green", "blue"])

## Ranges
#set ($range = [1..5])   ## creates [1, 2, 3, 4, 5]

## Maps (HashMaps)
#set ($person = {"name": "Jane", "role": "Developer"})

## Expressions
#set ($total = $price * $quantity)

## String concatenation
#set ($fullName = "${firstName} ${lastName}")

## Method calls
#set ($title = $currentPage.metadata.title)

The null-doesn’t-unset pitfall

When the right-hand side of a #set resolves to null, the variable retains its previous value. This is one of the most common sources of bugs in Velocity.

Velocity
## BUG: $subtitle leaks between iterations
#foreach ($item in $items)
  #set ($subtitle = $item.getChild("subtitle").textValue)
  <p>$subtitle</p>  ## shows PREVIOUS item's subtitle if current item has none
#end

The fix is to reset the variable before each assignment:

Velocity
## CORRECT: reset before each #set to avoid stale data
#foreach ($item in $items)
  #set ($subtitle = "")
  #set ($subtitle = $item.getChild("subtitle").textValue)
  #if ($_PropertyTool.isNotEmpty($subtitle))
    <p>$subtitle</p>
  #end
#end

Tip

For a deeper discussion of variable scoping, the reset-before-set idiom, and how #set behaves inside macros, see the Variable Scope article.

#if / #elseif / #else

Conditional directives control which portions of the template are rendered based on boolean expressions.

Velocity
#if ($condition)
  ## rendered when $condition is true
#elseif ($otherCondition)
  ## rendered when $otherCondition is true
#else
  ## rendered when none of the above are true
#end

Comparison operators

Operator Meaning Example
==Equal to#if ($status == "published")
!=Not equal to#if ($type != "hidden")
>Greater than#if ($count > 0)
<Less than#if ($count < 10)
>=Greater than or equal#if ($count >= 1)
<=Less than or equal#if ($count <= 100)
&&Logical AND#if ($a && $b)
||Logical OR#if ($a || $b)
!Logical NOT#if (!$isHidden)

#foreach

The #foreach directive iterates over a collection (list, array, map, or query result set) and repeats its body for each element.

Velocity
#foreach ($item in $collection)
  ## loop body — $item changes on each iteration
#end

Warning

Always use the reset-before-set pattern — #set ($var = "") then #set ($var = ...) — for any variable assigned inside a #foreach loop. Without this, null values from one iteration will leak the previous iteration’s data.

#break

#break exits the current #foreach loop immediately. Execution continues with whatever comes after the #end of the loop. This is useful when you need to find the first match in a collection or limit output based on a condition.

Velocity
#foreach ($item in $collection)
  #if ($someCondition)
    #break
  #end
#end

#break vs #stop

#break exits only the current #foreach loop and continues processing the rest of the template. #stop halts all template processing entirely. In nested loops, #break only exits the innermost loop.

#stop

#stop immediately halts all template processing. No further output is generated after this directive executes. This is useful for early exits when required data is missing or for debugging.

Velocity
#stop

#macro

The #macro directive defines a reusable block of Velocity code that can accept parameters. Macros help you avoid duplicating template logic across your Formats.

Defining and calling macros

Velocity
## Define a macro
#macro (renderCard $page $extraClass)
  #set ($rc_title = "")
  #set ($rc_title = $page.metadata.title)
  #set ($rc_desc = "")
  #set ($rc_desc = $page.metadata.description)
  <div class="card $extraClass">
    <h3><a href="$page.link">$rc_title</a></h3>
    #if ($_PropertyTool.isNotEmpty($rc_desc))
      <p>$rc_desc</p>
    #end
  </div>
#end

## Call the macro
#set ($results = $_.query().byContentType("page").maxResults(6).execute())
#foreach ($item in $results)
  #renderCard($item "card--featured")
#end

Scope leaking

Variables set with #set inside a macro are not scoped to that macro. They write directly to the same namespace as the calling Format, which means they can silently overwrite variables in the outer context.

Velocity
## BUG: macro overwrites the outer $title
#macro (renderHeading $page)
  #set ($title = $page.metadata.title)
  <h2>$title</h2>
#end

#set ($title = "My Page Title")
<h1>$title</h1>          ## "My Page Title"
#renderHeading($someOtherPage)
<h1>$title</h1>          ## now shows the OTHER page's title!

The fix is to prefix all variables inside macros with a unique identifier to avoid collisions:

Velocity
## CORRECT: prefix macro variables to avoid collisions
#macro (renderHeading $page)
  #set ($rh_title = $page.metadata.title)
  <h2>$rh_title</h2>
#end

#set ($title = "My Page Title")
<h1>$title</h1>          ## "My Page Title"
#renderHeading($someOtherPage)
<h1>$title</h1>          ## still "My Page Title"

Important

Velocity macros do not have their own scope. Every #set inside a macro writes to the calling template’s namespace. Use a naming prefix (e.g. $rc_, $nav_) for macro-internal variables. See the Variable Scope article for more details.

#import

The #import directive includes the contents of another Velocity Format from the CMS into the current template. This is how you share macros, utility functions, and common template fragments across multiple Formats.

Syntax

Velocity
## Import a shared macro library
#import ("_cms/formats/shared/macros/utility")

The path is relative to the root of the site in Cascade CMS. After importing, all macros and variables defined in the imported Format are available in the current template.

Common pattern: shared macro library

Velocity
## In _cms/formats/shared/macros/utility (the imported Format):
#macro (truncate $text $maxLength)
  #if ($_PropertyTool.isEmpty($text))
  #elseif ($text.length() > $maxLength)
    ${text.substring(0, $maxLength)}...
  #else
    $text
  #end
#end

## $date is a java.util.Date (metadata date fields already return one)
#macro (formatDate $date $pattern)
  #if ($date)$_DateTool.format($pattern, $date)#end
#end
Velocity
## In the main Format:
#import ("_cms/formats/shared/macros/utility")

## Now you can use the macros
<p>#truncate($currentPage.metadata.description, 150)</p>
<time>#formatDate($currentPage.metadata.startDate, "MMMM d, yyyy")</time>

Performance

Each #import directive requires a round trip to the database to fetch the Format’s contents. Consolidate related macros into fewer Formats to minimize the number of #import calls. See the Best Practices for Performance article for detailed guidance.

#evaluate

#evaluate takes a string and processes it as Velocity code at runtime. The most common use in Cascade is calling a macro whose name is stored in a variable: you build the directive call as a string, then evaluate it. This lets a single dispatcher macro route to any named macro without a chain of #if/#elseif branches.

Syntax

Velocity
#evaluate($stringContainingVelocityCode)

Examples

A dispatcher macro that calls any named macro by building the call string at runtime. The $"+"data" split prevents Velocity from trying to resolve $data inside the string literal before #evaluate runs.

Velocity
## Dispatcher: call any macro by name
#macro(runMacro $name $data $origin)
  #set ($macroBuild = "#" + $name + "($" + "data $" + "origin)")
  #evaluate($macroBuild)
#end

## Usage — $name is resolved at call time
#runMacro("heroBlock" $pageData "default")

If the macro lives in another Format, #import that Format by path first, then dispatch with #runMacro. Use a site:// path for a Format in another site.

Velocity
## Import the Format that defines the macro (path string, not an API object)
#import ("site://Global/_components/hero")

## Now dispatch to the macro by variable name
#set ($macroName = "heroBlock")
#runMacro($macroName $pageData "default")

Use sparingly

#evaluate executes code that isn’t visible in the Format, which makes templates harder to read and debug. Only evaluate strings you control. When the set of possible values is small and known, #if/#elseif branching or #macro parameters are usually clearer.

#define

#define captures a block of Velocity code that is not evaluated immediately. Instead, the block is evaluated each time the variable is referenced. This makes it behave like a reusable template fragment that always reflects the current state of your variables.

Syntax

Velocity
#define ($blockName)
  ## template content here — evaluated later, not now
#end

How it differs from #set

#set evaluates the right-hand side immediately and stores the result as a fixed value. #define stores the block as unevaluated code and re-evaluates it every time the variable is referenced. This distinction matters whenever the variables inside the block change between the point of definition and the point of use.

Velocity
## #set evaluates immediately (snapshot)
#set ($count = 0)
#set ($message = "Count is $count")
#set ($count = 5)
$message  ## outputs: Count is 0

## #define evaluates lazily (live feed)
#set ($count = 0)
#define ($message)Count is $count#end
#set ($count = 5)
$message  ## outputs: Count is 5

Examples

The most common use for #define is a template fragment re-evaluated inside a loop. Because the block evaluates fresh each iteration, it picks up the current loop variable.

Velocity
## Define a card template once, reuse it in the loop
#define ($card)
  <div class="card">
    <h3><a href="$item.link">$item.metadata.title</a></h3>
    <p>$item.metadata.description</p>
  </div>
#end

#set ($results = $_.query().byContentType("page").execute())
#foreach ($item in $results)
  $card  ## re-evaluates each iteration with the current $item
#end

#define vs #macro

Both #define and #macro let you reuse template blocks, but they work differently:

Feature #define #macro
ParametersNone — relies on variables already in scopeAccepts explicit parameters
EvaluationLazy — re-evaluated each time the variable is referencedEvaluated when called
ScopeReads from the current scope at evaluation timeShares the caller’s scope (no isolation)
Reuse across FormatsNo — local to the current templateYes — can be imported via #import
Best forTemplate fragments reused within one FormatUtility functions shared across Formats

Tip

Think of #set as taking a photo (captured once) and #define as a live camera feed (always shows the current state when viewed). Use #define when you want a reusable template block that adapts to changing variables. Use #set when you want a fixed snapshot. See the Variable Scope article for more examples.

#parse

The #parse directive includes and evaluates another Velocity template inline. The parsed template shares the same variable context as the calling template—variables set before #parse are available inside the parsed Format, and variables set inside the parsed Format are visible in the caller after the call.

Syntax

Velocity
#parse ("_cms/formats/shared/header-fragment")

The path is relative to the root of the site in Cascade CMS, just like #import.

Differences from #import

Feature #import #parse
Primary useLoading macro definitionsIncluding template fragments that produce output
Variable sharingMacros become available; variable side effects may occurFull variable context is shared both ways
OutputTypically produces no direct output (macro definitions only)Output is rendered inline at the point of the #parse call
Database costOne round trip per callOne round trip per call

Examples

Since #parse shares the caller’s variable context, set variables before the call to control how the parsed template behaves. This is how you pass “parameters” to a parsed Format.

Velocity
## Main Format: set variables, then parse the fragment
#set ($navStyle = "horizontal")
#set ($showSearch = true)
#parse ("_cms/formats/shared/navigation")
Velocity
## Inside _cms/formats/shared/navigation:
## $navStyle and $showSearch are available here from the caller
<nav class="nav nav--$navStyle">
  <ul>
    #set ($navItems = $_.query().byContentType("nav-item").sortBy("title").sortDirection("asc").execute())
    #foreach ($navItem in $navItems)
      <li><a href="$navItem.link">$navItem.metadata.title</a></li>
    #end
  </ul>
  #if ($showSearch)
    <form class="nav-search" action="/search">
      <input type="search" name="q" placeholder="Search..." />
    </form>
  #end
</nav>

Performance

Like #import, each #parse call requires a database round trip. Use naming prefixes in parsed Formats to avoid variable collisions with the caller, just as you would with macros.