
# Velocity directives

> Reference for the core Velocity directives used in Cascade CMS, including #set, #if, #foreach, #break, #stop, #macro, #import, #evaluate, #define, and #parse, with practical examples and common pitfalls.

URL: https://www.hannonhill.com/cascadecms/latest/articles/developing-in-cascade/script-formats/velocity-directives.html
category: Developing in Cascade
article-type: reference
content-kind: article
release-availability: all
tags: velocity
updated: 2026-10-06

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](https://www.hannonhill.com/cascadecms/latest/articles/developing-in-cascade/script-formats/query-tool-directives.html) article.

| Directive | What it does |
| --- | --- |
| [`#set`](#velocity-directives-set) | Assign a value to a variable. |
| [`#if / #elseif / #else`](#velocity-directives-if) | Render content conditionally. |
| [`#foreach`](#velocity-directives-foreach) | Iterate over a collection. |
| [`#break`](#velocity-directives-break) | Exit the current `#foreach` loop early. |
| [`#stop`](#velocity-directives-stop) | Halt all template processing immediately. |
| [`#macro`](#velocity-directives-macro) | Define a reusable, parameterized block of code. |
| [`#import`](#velocity-directives-import) | Pull macros and shared code from another Format. |
| [`#evaluate`](#velocity-directives-evaluate) | Execute a Velocity string built at runtime. |
| [`#define`](#velocity-directives-define) | Capture a block of template content for later reuse. |
| [`#parse`](#velocity-directives-parse) | Include 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](https://www.hannonhill.com/cascadecms/latest/articles/developing-in-cascade/script-formats/velocity-variable-scope.html) 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
```

Cascade CMS provides the `$_PropertyTool` which offers reliable methods for checking whether a value is null, empty, or blank. This is the recommended approach over manual null/empty checks.

```velocity
## $_PropertyTool.isNotEmpty() — true when value is not null AND not empty
#set ($title = $currentPage.metadata.title)
#if ($_PropertyTool.isNotEmpty($title))
  <h1>$title</h1>
#end

## $_PropertyTool.isEmpty() — true when value is null or empty
#if ($_PropertyTool.isEmpty($currentPage.metadata.description))
  <meta name="description" content="Default description" />
#else
  <meta name="description" content="$currentPage.metadata.description" />
#end
```

> **Tip**
>
> Prefer `$_PropertyTool.isNotEmpty()` and `$_PropertyTool.isEmpty()` over manual checks like `$var != ""`. The PropertyTool handles null, empty strings, and edge cases consistently, making your conditionals more reliable.

```velocity
## Check if a structured data field exists and has content
#set ($heroImage = "")
#set ($heroImage = $currentPage.getStructuredDataNode("hero-image"))
#if ($_PropertyTool.isNotEmpty($heroImage) && $heroImage.asset)
  <img src="$heroImage.asset.link" alt="$currentPage.metadata.title" />
#end

## Conditionally render based on metadata
#set ($pageType = "")
#set ($pageType = $currentPage.metadata.getDynamicField("page-type").value)
#if ($pageType == "landing")
  ## render landing page layout
#elseif ($pageType == "detail")
  ## render detail page layout
#else
  ## render default layout
#end

## Check if a collection has results
#set ($results = $_.query().byContentType("news").execute())
#if ($_PropertyTool.isNotEmpty($results))
  ## render results
#else
  <p>No news articles found.</p>
#end
```

> **Note**
>
> In Velocity, `#if ($var)` evaluates to false when `$var` is null or boolean false, but its behavior with empty strings can be inconsistent. Using `$_PropertyTool.isEmpty()` and `$_PropertyTool.isNotEmpty()` removes that ambiguity. If your value is being assigned inside a loop, see the [#set](#velocity-directives-set) section for the reset-before-set pattern.

### 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
```

Velocity provides a built-in `$foreach` object with useful properties inside any `#foreach` loop:

| Property | Description | Example value (3 items) |
| --- | --- | --- |
| `$foreach.index` | Zero-based index of the current iteration | 0, 1, 2 |
| `$foreach.count` | One-based count of the current iteration | 1, 2, 3 |
| `$foreach.hasNext` | True if there are more items after this one | true, true, false |
| `$foreach.first` | True on the first iteration only | true, false, false |
| `$foreach.last` | True on the last iteration only | false, false, true |

```velocity
#set ($news = $_.query().byContentType("news-article").sortBy("startDate").sortDirection("desc").maxResults(10).execute())
#foreach ($article in $news)
  <article>
    <h2><a href="$article.link">$article.metadata.title</a></h2>
    <p>$article.metadata.description</p>
  </article>
#end
```

```velocity
#set ($slides = $currentPage.getStructuredDataNodes("slide"))
#foreach ($slide in $slides)
  #set ($heading = "")
  #set ($heading = $slide.getChild("heading").textValue)
  #set ($caption = "")
  #set ($caption = $slide.getChild("caption").textValue)
  <div class="slide #if($foreach.first)slide--active#end">
    #if ($_PropertyTool.isNotEmpty($heading))
      <h2>$heading</h2>
    #end
    #if ($_PropertyTool.isNotEmpty($caption))
      <p>$caption</p>
    #end
  </div>
#end
```

```velocity
## Build a comma-separated tag list
#set ($tags = $currentPage.metadata.getDynamicField("tags").values)
#foreach ($tag in $tags)${tag}#if($foreach.hasNext), #end#end
## Output: News, Featured, Campus Life
```

```velocity
## Render a table from structured data with rows and columns
#set ($rows = $currentPage.getStructuredDataNodes("row"))
<table>
#foreach ($row in $rows)
  <tr>
    #set ($cells = $row.getChildren("cell"))
    #foreach ($cell in $cells)
      <td>$cell.textValue</td>
    #end
  </tr>
#end
</table>
```

> **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
```

Use `#break` to stop iterating as soon as you've found what you're looking for, avoiding unnecessary processing of the remaining items.

```velocity
## Find the first featured news article
#set ($featured = false)
#set ($results = $_.query().byContentType("news").execute())
#foreach ($article in $results)
  #set ($isFeatured = "")
  #set ($isFeatured = $article.metadata.getDynamicField("featured").value)
  #if ($isFeatured == "Yes")
    #set ($featured = $article)
    #break
  #end
#end

#if ($featured)
  <div class="featured-article">
    <h2>$featured.metadata.title</h2>
    <p>$featured.metadata.description</p>
  </div>
#end
```

Use `#break` to cap output at a specific count when the query tool's `.maxResults()` alone isn't enough — for example, when filtering by a condition inside the loop.

```velocity
## Show only the first 3 visible items from a structured data group
#set ($items = $currentPage.getStructuredDataNodes("item"))
#set ($shown = 0)
#foreach ($item in $items)
  #set ($isVisible = "")
  #set ($isVisible = $item.getChild("visible").textValue)
  #if ($isVisible == "Yes")
    <div class="item">$item.getChild("title").textValue</div>
    #set ($shown = $shown + 1)
    #if ($shown >= 3)
      #break
    #end
  #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
```

Use `#stop` as a guard clause at the top of a Format to bail out immediately when required data is missing, preventing partial or broken output.

```velocity
## Guard clause: stop if the page has no content type assigned
#set ($contentType = "")
#set ($contentType = $currentPage.metadata.getDynamicField("content-type").value)
#if ($_PropertyTool.isEmpty($contentType))
  <!-- Error: no content type configured for this page -->
  #stop
#end

## Rest of the template only runs if $contentType is set
<div class="$contentType">
  ## ...
</div>
```

Render a friendly empty state and stop, so the rest of the template (which assumes items exist) doesn’t try to iterate or render headings.

```velocity
#set ($items = $currentPage.getStructuredDataNodes("item"))
#if ($items.isEmpty())
  <p class="empty-state">No items configured yet.</p>
  #stop
#end

## The rest of the template only runs when $items has content
<ul>
  #foreach ($item in $items)
    <li>$item.getChild("title").textValue</li>
  #end
</ul>
```

Temporarily place `#stop` after a `<pre>` dump to inspect what variables contain at that point in the template, without rendering any output below it.

```velocity
## Temporarily stop execution to inspect variables at this point
<pre>
  title: $currentPage.metadata.title
  results count: $results.size()
</pre>
#stop
## Everything below is skipped during debugging
```

> **Tip**
>
> Place `#stop` after a `<pre>` block that dumps variable values to create a quick-and-dirty debugging checkpoint. Remember to remove it before publishing.

## #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](https://www.hannonhill.com/cascadecms/latest/articles/developing-in-cascade/script-formats/velocity-variable-scope.html) 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](https://www.hannonhill.com/cascadecms/latest/articles/developing-in-cascade/script-formats/velocity-best-practices.html) 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")
```

Evaluate a Velocity snippet stored in a structured data field. Use this only when you control what goes into that field — never evaluate untrusted user input.

```velocity
#set ($snippet = "")
#set ($snippet = $currentPage.getStructuredDataNode("velocity-snippet").textValue)
#if ($_PropertyTool.isNotEmpty($snippet))
  #evaluate($snippet)
#end
```

> **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
```

Use `#define` to prepare layout sections that adapt to context. The same block can appear in different places, picking up whatever data the surrounding code has set.

```velocity
## Define a sidebar block that always reflects the latest $sidebarItems
#define ($sidebar)
  <aside class="sidebar">
    #foreach ($link in $sidebarItems)
      <a href="$link.link">$link.metadata.title</a>
    #end
  </aside>
#end

## Use it with different data sets on the same page
#set ($sidebarItems = $_.query().byContentType("quick-link").maxResults(5).execute())
$sidebar  ## renders quick links

#set ($sidebarItems = $_.query().byContentType("related-resource").maxResults(5).execute())
$sidebar  ## renders related resources with the same markup
```

Define a small badge once, reuse it inside a loop. Each reference re-evaluates `$foreach.count` and `$items.size()` against the current loop state.

```velocity
#define ($badge)
  <span class="badge">Item $foreach.count of $items.size()</span>
#end

#set ($items = $_.query().byContentType("news").execute())
#foreach ($item in $items)
  $badge <a href="$item.link">$item.metadata.title</a>
#end
```

Define the link markup once, reuse it after each card. The `$item` reference resolves lazily at render time, so each call picks up the current loop item.

```velocity
#define ($readMore)
  <a href="$item.link" class="read-more">Read more →</a>
#end

#foreach ($item in $featured)
  <article>
    <h3>$item.metadata.title</h3>
    <p>$item.metadata.description</p>
    $readMore
  </article>
#end
```

### #define vs #macro

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

| Feature | #define | #macro |
| --- | --- | --- |
| Parameters | None — relies on variables already in scope | Accepts explicit parameters |
| Evaluation | Lazy — re-evaluated each time the variable is referenced | Evaluated when called |
| Scope | Reads from the current scope at evaluation time | Shares the caller’s scope (no isolation) |
| Reuse across Formats | No — local to the current template | Yes — can be imported via `#import` |
| Best for | Template fragments reused within one Format | Utility 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](https://www.hannonhill.com/cascadecms/latest/articles/developing-in-cascade/script-formats/velocity-variable-scope.html) 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 use | Loading macro definitions | Including template fragments that produce output |
| Variable sharing | Macros become available; variable side effects may occur | Full variable context is shared both ways |
| Output | Typically produces no direct output (macro definitions only) | Output is rendered inline at the point of the `#parse` call |
| Database cost | One round trip per call | One 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>
```

A common Cascade CMS pattern is to use `#parse` to assemble a page layout from multiple shared Formats, each handling a different section of the page.

```velocity
## Main page Format
#import ("_cms/formats/shared/macros/utility")
<!DOCTYPE html>
<html lang="en">
<head>
  <title>$currentPage.metadata.title</title>
  #parse ("_cms/formats/shared/head-assets")
</head>
<body>
  #parse ("_cms/formats/shared/site-header")

  <main>
    ## Page-specific content here
    $currentPage.getStructuredDataNode("body").textValue
  </main>

  #parse ("_cms/formats/shared/site-footer")
</body>
</html>
```

Variables set inside a `#parse`d Format remain in scope after the call returns. This can be useful (the parsed template can compute values for the caller), but it can also cause unexpected collisions.

```velocity
## Inside _cms/formats/shared/head-assets:
#set ($siteName = "My University")
#set ($cssVersion = "3.2.1")
<link rel="stylesheet" href="/css/main.css?v=$cssVersion" />
```

```velocity
## Back in the main Format, after the #parse call:
#parse ("_cms/formats/shared/head-assets")
## $siteName and $cssVersion are now available here
<title>$currentPage.metadata.title | $siteName</title>
```

> **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.
