Formats

Variable scope in Velocity

Global vs loop-scoped variables

Velocity exposes two kinds of variables depending on where they come from:

  • Global variables like  $currentPage$_, and  $_DateTool are set once when the Format begins executing and remain available everywhere in the template.  $currentPage always refers to the page being rendered.
  • Loop-scoped variables like  $item in  #foreach($item in ...) change on each iteration and only carry meaning inside the loop body.

Understanding which variable points to which asset is the single most important scoping concept in Cascade CMS Velocity development.

 

Tip$currentPage refers to the page the Velocity Format is rendered on. Inside a  #foreach loop you’re iterating over a list of items (pages, groups, assets), so the iteration variable changes on each pass, but  $currentPage is a global variable and never changes.

Common mistake — $currentPage inside loops

The most frequent scoping bug is referencing  $currentPage inside a loop when you actually mean the iteration variable. Consider a Format that queries for pages and lists them:

## BUG: $currentPage always refers to the rendering page, not the query result
#set ($results = $_query.byContentType("page").execute())
#foreach ($child in $results)
<h2>$currentPage.metadata.title</h2>
<p>$currentPage.metadata.description</p>
#end

Every iteration prints the  rendering page’s title and description because  $currentPage never changes. The loop variable  $child is the one that advances through the results on each pass.

The fix is straightforward—reference  $child instead:

## CORRECT: use the loop variable for each result's data
#set ($results = $_query.byContentType("page").execute())
#foreach ($child in $results)
<h2>$child.metadata.title</h2>
<p>$child.metadata.description</p>
#end
 

Warning: This bug produces output that  looks correct at first glance because the rendering page’s title renders without errors. The problem only becomes visible when you have multiple results and notice every row shows the same data.

#set does not unset

In Velocity, #set($var = $something) does not clear $var when $something resolves to null. The variable retains whatever value it held before the assignment. This is a common source of stale data in loops.

## BUG: $subtitle leaks between iterations#set ($items = $currentPage.getStructuredDataNodes("row"))
#foreach ($item in $items)
  #set ($subtitle = $item.getChild("subtitle").value)
  ## If this child has no "subtitle" field, $subtitle
  ## still holds the PREVIOUS child's value
  <p>$subtitle</p>
#end

When a row lacks a “subtitle” field, the #set receives null, but $subtitle keeps the last non-null value. The output leaks one row’s subtitle into the next row.

The fix is to reset the variable to an empty string before each #set call:

## CORRECT: reset before each #set to avoid stale data
#set ($items = $currentPage.getStructuredDataNodes("row"))
#foreach ($item in $items)
  #set ($subtitle = "")
  #set ($subtitle = $item.getChild("subtitle").value)
  #if ($subtitle != "")
    <p>$subtitle</p>
  #end
#end

Tip: The reset-before-set pattern (#set($var = "") followed by #set($var = ...)) is the standard Velocity idiom for handling potentially null values inside loops. Always reset to an empty string or a known default before the real assignment.

Macro 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 overwrite variables in the outer context.

## BUG: the macro overwrites the outer $title
#macro (renderCard $page)
   #set ($title = $page.metadata.title)
   #set ($link = $page.link)
   <div class="card">
      <a href="$link">$title</a>
   </div>
#end

#set ($title = "My Page Title")
<h1>$title</h1> ## outputs "My Page Title"
#renderCard($someOtherPage)
<h1>$title</h1> ## outputs the other page's title!

After #renderCard executes, $title in the outer context has been silently replaced with $someOtherPage’s title.

The fix is to use a naming prefix convention for variables inside macros so they don’t collide with the caller’s variables:

## CORRECT: prefix macro variables to avoid collisions
#macro (renderCard $page)
   #set ($card_title = $page.metadata.title)
   #set ($card_link = $page.link)
   <div class="card">
      <a href="$card_link">$card_title</a>
   </div>
#end

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

Important: Velocity macros do not have their own scope. Every #set inside a macro writes directly to the same namespace as the calling Format. Use a naming prefix (e.g. $card_, $nav_) for variables inside macros to prevent accidental overwrites.

#define vs #set

#set evaluates the right-hand side immediately and stores the result. #define captures a block of Velocity code that is evaluated later, each time the variable is referenced. This distinction matters when the surrounding context changes between definition and use.

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

#set captured the value of $count at the moment of assignment. Changing $count afterward has no effect on $message.

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

#define deferred evaluation until $message was referenced. At that point, $count was already 5.

This makes #define useful for creating reusable template blocks. Here is a simple example with an array:

## Define a list item template
#define ($listItem)
   <li>$fruit</li>
#end

#set ($fruits = ["Apple", "Banana", "Cherry"])

<ul>
#foreach ($fruit in $fruits)
   $listItem ## re-evaluates each time, picking up the current $fruit
#end
</ul>

## Output:
## <ul>
##    <li>Apple</li>
##    <li>Banana</li>
##    <li>Cherry</li>
## </ul>

The same pattern works well with Cascade query results:

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

#set ($results = $_query.byContentType("page").execute())
#foreach ($item in $results)
   $card ## evaluates fresh each iteration with the current $item
#end

In both cases the block is defined once but picks up the current loop variable on each iteration because #define defers evaluation until the variable is referenced. Use #set when you want a fixed snapshot of a value. Use #define when you want a reusable template block that should pick up the latest context.

Tip: A good mental model: #set is like taking a photo (captured once), while #define is like a live camera feed (always shows the current state when viewed).