SOLVED

Velocity variables in multiple scripts causing confusion

Go to solution
MattDePalm
Level 2

Velocity variables in multiple scripts causing confusion

Hi Everyone, 

 

I seemed to have unknowingly stumbled upon Velocity's ability to declare and reference variables across multiple tokens. After removing one debug token from an email, subsequent script tokens stop evaluating and fall back to their default values. Re-adding the debug token “fixes” the issue, but I don’t fully understand why.

 

Context

  • I’m working on an auto-renewal email that pulls fields from a related Opportunity with Velocity in a few tokens (Pre-AR notification date, plus 30-/90-day offsets).

  • I had a temporary “debug” Velocity token in the template that printed out all calculated dates and metadata. That worked perfectly.

  • Once I removed that debug token (to clean up before final approval), the main script token suddenly rendered its fallback (“no date found”) for every recipient—even though the underlying Opportunity data was unchanged.

  • If I re-insert the debug token back into the email, everything renders correctly again.

  • In the attached image Token 1 is the debugging token, and Tokens 2 and 3 are the production ones.

 

Issue Statement

When multiple Velocity script tokens exist in the same email asset, Marketo appears to stop processing subsequent tokens once the first one is removed—even if those other tokens haven’t changed. I’m looking for insight into how Marketo’s Velocity engine declares or scopes its variables and why the presence (or absence) of an earlier script token affects later ones. Has anyone seen this behavior, and can you explain what’s happening under the hood or suggest a better workaround?

 

ONGOING_NTR_CN_Auto Renewal_V2.01. SalesOS only email - 30 days C2C 2025-06-23 at 12.34.29 PM.jpg

Token Scripts:

Token 1 (Debugging):

 

## --- Set required Velocity variables for timezone and formatting ---
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601DateOnly = "yyyy-MM-dd" )

## --- Format today's date as yyyy-MM-dd string ---
#set( $today = $date.format($ISO8601DateOnly, $calNow, $defaultLocale, $defaultTimeZone) )

## --- Find first matching opportunity ---
#set($matchingOpp = "")
#foreach($opp in $OpportunityList)
  #if(
    $opp.Pre_Auto_Renewal_Notification_Date__c &&
    $opp.Stage != "Closed Lost" &&
    !$opp.Auto_Renewal_Opt_Out__c &&
    $opp.Type == "Auto Renewal - Pending"
  )
    #set($matchingOpp = $opp)
    #break
  #end
#end

## --- Output calculated date values ---
#if($matchingOpp != "" && $matchingOpp.Pre_Auto_Renewal_Notification_Date__c)

  ## Set Opp Name
  #set($oppName = $matchingOpp.Name)
  ## Parse string to Date
  #set($baseDate = $convert.parseDate($matchingOpp.Pre_Auto_Renewal_Notification_Date__c, $ISO8601DateOnly, $defaultLocale, $defaultTimeZone))

  ## Convert to Calendar and add days
  #set($cal30 = $convert.toCalendar($baseDate))
  #set($cal90 = $convert.toCalendar($baseDate))
  $cal30.add($calConst.DATE, 30)
  $cal90.add($calConst.DATE, 90)

  ## Format and print output
  Opp Name: $oppName<br>
  Pre AR Date: $date.format($ISO8601DateOnly, $baseDate, $defaultLocale, $defaultTimeZone)<br/>
  +30 Days: $date.format($ISO8601DateOnly, $cal30, $defaultLocale, $defaultTimeZone)<br/>
  +90 Days: $date.format($ISO8601DateOnly, $cal90, $defaultLocale, $defaultTimeZone)

#else
no date found
#end

Token 2 (30 day offset) 

## --- Set required Velocity variables for timezone and formatting ---
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601DateOnly = "MM/dd/yyyy" )

## --- Find first matching opportunity ---
#set($matchingOpp = "")
#foreach($opp in $OpportunityList)
  #if(
    $opp.Pre_Auto_Renewal_Notification_Date__c &&
    $opp.Stage != "Closed Lost" &&
    !$opp.Auto_Renewal_Opt_Out__c &&
    $opp.Type == "Auto Renewal - Pending"
  )
    #set($matchingOpp = $opp)
    #break
  #end
#end

## --- Output calculated date values ---
#if($matchingOpp != "" && $matchingOpp.Pre_Auto_Renewal_Notification_Date__c)

  ## Parse string to Date
  #set($baseDate = $convert.parseDate($matchingOpp.Pre_Auto_Renewal_Notification_Date__c, $ISO8601DateOnly, $defaultLocale, $defaultTimeZone))

  ## Convert to Calendar and add days
  #set($cal30 = $convert.toCalendar($baseDate))
  #set($cal90 = $convert.toCalendar($baseDate))
  $cal30.add($calConst.DATE, 30)
  $cal90.add($calConst.DATE, 90)

  ## Format and print output
 $date.format($ISO8601DateOnly, $cal30, $defaultLocale, $defaultTimeZone)#else
no date found#end

 Token 3 (90 Offset):

## --- Set required Velocity variables for timezone and formatting ---
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601DateOnly = "MM/dd/yyyy" )

## --- Find first matching opportunity ---
#set($matchingOpp = "")
#foreach($opp in $OpportunityList)
  #if(
    $opp.Pre_Auto_Renewal_Notification_Date__c &&
    $opp.Stage != "Closed Lost" &&
    !$opp.Auto_Renewal_Opt_Out__c &&
    $opp.Type == "Auto Renewal - Pending"
  )
    #set($matchingOpp = $opp)
    #break
  #end
#end

## --- Output calculated date values ---
#if($matchingOpp != "" && $matchingOpp.Pre_Auto_Renewal_Notification_Date__c)

  ## Parse string to Date
  #set($baseDate = $convert.parseDate($matchingOpp.Pre_Auto_Renewal_Notification_Date__c, $ISO8601DateOnly, $defaultLocale, $defaultTimeZone))

  ## Convert to Calendar and add days
  #set($cal30 = $convert.toCalendar($baseDate))
  #set($cal90 = $convert.toCalendar($baseDate))
  $cal30.add($calConst.DATE, 30)
  $cal90.add($calConst.DATE, 90)

  ## Format and print output
 $date.format($ISO8601DateOnly, $cal90, $defaultLocale, $defaultTimeZone)#else
no date found#end
2 ACCEPTED SOLUTIONS

Accepted Solutions
SanfordWhiteman
Level 10 - Community Moderator

Re: How to properly reference Velocity variables in multiple scripts

Of course!

#set( $CustomDateFormat = "MM/dd/yyyy" )

 

That’s what you pass to $date.format(), not to $convert.parseDate().

View solution in original post

SanfordWhiteman
Level 10 - Community Moderator

Re: How to properly reference Velocity variables in multiple scripts

Yep, although I’d use a real Boolean as your check as opposed to an empty String. Plus a little cleanup in there to use .equals() and avoid unnecessary output.

## --- Set required Velocity variables for timezone and formatting ---
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601DateOnly = "yyyy-MM-dd" )
#set( $CustomDateFormat = "MM/dd/yyyy" )

## --- Find first matching opportunity ---
#set($matchingOpp = false)
#foreach($opp in $OpportunityList)
#if(
    $opp.Pre_Auto_Renewal_Notification_Date__c &&
    !$opp.Stage.equals("Closed Lost") &&
    $opp.Auto_Renewal_Opt_Out__c.equals(false) &&
    $opp.Type.equals("Auto Renewal - Pending")
  )
    #set($matchingOpp = $opp)
    #break
  #end
#end

## --- Output calculated date values ---
#if( $matchingOpp )
  ## Parse string to Date
  #set($baseDate = $convert.parseDate($matchingOpp.Pre_Auto_Renewal_Notification_Date__c, $ISO8601DateOnly, $defaultLocale, $defaultTimeZone))

  ## Convert to Calendar and add days
  #set($cal30 = $convert.toCalendar($baseDate))
  #set($cal90 = $convert.toCalendar($baseDate))
  #set( $void = $cal30.add($calConst.DATE, 30))
  #set( $void = $cal90.add($calConst.DATE, 90))

  ## Format and print output
 $date.format($CustomDateFormat, $cal90, $defaultLocale, $defaultTimeZone)##
#else
no date found##
#end

 

View solution in original post

9 REPLIES 9
SanfordWhiteman
Level 10 - Community Moderator

Re: How do it properly Velocity variables in multiple scripts


I seemed to have unknowingly stumbled upon Velocity's ability to declare and reference variables across multiple tokens.

Of course. A Marketo email is one big Velocity template!

 

All the VTL components share a single scope, just as they would if placed end-to-end in a single {{my.token}}. Splitting across multiple tokens is highly recommended because it allows modular development. Don’t know where I’d be without the ability to use multiple tokens — some inherited, some local.

 


Issue Statement

When multiple Velocity script tokens exist in the same email asset, Marketo appears to stop processing subsequent tokens once the first one is removed


No, that’s not true.

 

Most likely cause is you have certain fields checked off in the tree in Script Editor in Token 1, but you didn’t check them in Token 2/3. (This a vital feature as only one token needs to have the fields checked. But you need to include that one!)

 

 

 

 

MattDePalm
Level 2

Re: How do it properly Velocity variables in multiple scripts

Hi Sanford, 

 

Thank you so much for the quick reply! That was totally it - the initial debugging token had all of the field checked off in the template but the production tokens didn't. 

 

However, that brings me to my next question. I'm now seeing the following values that I didn't see before.

 

$cal30.add($calConst.DATE, 30) $cal90.add($calConst.DATE, 90) $date.format($ISO8601DateOnly, $cal30, $defaultLocale, $defaultTimeZone)

 

pre_ar_screenshot2_250623.jpg

SanfordWhiteman
Level 10 - Community Moderator

Re: How do it properly Velocity variables in multiple scripts

When you see raw Velocity code, that’s because there was an error calling a method (this is how Velocity avoids throwing fatal errors all the time, although it’s still possible to have fatal errors in lots of cases).

 

To help further I’d need to see the raw output of

#foreach( $oppty in $OpportunityList) 
#foreach( $kv in $oppty.entrySet() )
$kv.getValue().class $kv.getKey() $kv.getValue()
#end
#end
MattDePalm
Level 2

Re: How to properly reference Velocity variables in multiple scripts

Hi Sandy, 

 

Here's the raw ouput from the script provided:

class java.lang.String
Type
Renewal

class java.lang.String
Stage
Stage 1: Unengaged Renewal

class java.lang.String
Auto_Renewal_Opt_Out__c
1

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2025-06-23

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Renewal

class java.lang.String
Stage
Stage 1: Unengaged Renewal

class java.lang.String
Auto_Renewal_Opt_Out__c
1

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2025-06-23

class java.lang.String
Name
Ryse Renewal Q3 2025

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Renewal

class java.lang.String
Stage
Stage 1: Unengaged Renewal

class java.lang.String
Auto_Renewal_Opt_Out__c
1

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2025-06-23

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Renewal

class java.lang.String
Stage
Stage 1: Unengaged Renewal

class java.lang.String
Auto_Renewal_Opt_Out__c
1

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2025-06-23

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Renewal

class java.lang.String
Stage
Stage 1: Unengaged Renewal

class java.lang.String
Auto_Renewal_Opt_Out__c
1

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2025-06-23

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Auto Renewal - Paying

class java.lang.String
Stage
Closed Won

$kv.getValue().class
Auto_Renewal_Opt_Out__c
$kv.getValue()

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2024-06-23

class java.lang.String
Name
Ryse Renewal Q3 2024

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Auto Renewal - Paying

class java.lang.String
Stage
Closed Won

$kv.getValue().class
Auto_Renewal_Opt_Out__c
$kv.getValue()

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2024-06-23

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
Auto Renewal - Paying

class java.lang.String
Stage
Closed Won

$kv.getValue().class
Auto_Renewal_Opt_Out__c
$kv.getValue()

class java.lang.String
Pre_Auto_Renewal_Notification_Date__c
2024-06-23

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()

class java.lang.String
Type
New Business

class java.lang.String
Stage
Closed Won

$kv.getValue().class
Auto_Renewal_Opt_Out__c
$kv.getValue()

$kv.getValue().class
Pre_Auto_Renewal_Notification_Date__c
$kv.getValue()

class java.lang.String
Name
[REDACTED]

$kv.getValue().class
Opportunity_Status_StageName__c
$kv.getValue()
SanfordWhiteman
Level 10 - Community Moderator

Re: How to properly reference Velocity variables in multiple scripts

You changed the value of $ISO8601DateOnly to something that’s definitely not ISO 8601. It must be

#set( $ISO8601DateOnly = "yyyy-MM-dd" )

 

MattDePalm
Level 2

Re: How to properly reference Velocity variables in multiple scripts

Thank you, Sandy!

 

Is there a way to format the date to MM/DD/YYYY?

SanfordWhiteman
Level 10 - Community Moderator

Re: How to properly reference Velocity variables in multiple scripts

Of course!

#set( $CustomDateFormat = "MM/dd/yyyy" )

 

That’s what you pass to $date.format(), not to $convert.parseDate().

MattDePalm
Level 2

Re: How to properly reference Velocity variables in multiple scripts

I think I understand. So is this then the correct iteration of script?

 

## --- Set required Velocity variables for timezone and formatting ---
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601DateOnly = "yyyy-MM-dd" )
#set( $CustomDateFormat = "MM/dd/yyyy" )

## --- Find first matching opportunity ---
#set($matchingOpp = "")
#foreach($opp in $OpportunityList)
  #if(
    $opp.Pre_Auto_Renewal_Notification_Date__c &&
    $opp.Stage != "Closed Lost" &&
    !$opp.Auto_Renewal_Opt_Out__c &&
    $opp.Type == "Auto Renewal - Pending"
  )
    #set($matchingOpp = $opp)
    #break
  #end
#end

## --- Output calculated date values ---
#if($matchingOpp != "" && $matchingOpp.Pre_Auto_Renewal_Notification_Date__c)

  ## Parse string to Date
  #set($baseDate = $convert.parseDate($matchingOpp.Pre_Auto_Renewal_Notification_Date__c, $ISO8601DateOnly, $defaultLocale, $defaultTimeZone))

  ## Convert to Calendar and add days
  #set($cal30 = $convert.toCalendar($baseDate))
  #set($cal90 = $convert.toCalendar($baseDate))
  $cal30.add($calConst.DATE, 30)
  $cal90.add($calConst.DATE, 90)

  ## Format and print output
 $date.format($CustomDateFormat, $cal90, $defaultLocale, $defaultTimeZone)#else
no date found#end

 

I initialized $CustomDateFormat at the top of the template and passed the variable to $date.format at the bottom. 

SanfordWhiteman
Level 10 - Community Moderator

Re: How to properly reference Velocity variables in multiple scripts

Yep, although I’d use a real Boolean as your check as opposed to an empty String. Plus a little cleanup in there to use .equals() and avoid unnecessary output.

## --- Set required Velocity variables for timezone and formatting ---
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601DateOnly = "yyyy-MM-dd" )
#set( $CustomDateFormat = "MM/dd/yyyy" )

## --- Find first matching opportunity ---
#set($matchingOpp = false)
#foreach($opp in $OpportunityList)
#if(
    $opp.Pre_Auto_Renewal_Notification_Date__c &&
    !$opp.Stage.equals("Closed Lost") &&
    $opp.Auto_Renewal_Opt_Out__c.equals(false) &&
    $opp.Type.equals("Auto Renewal - Pending")
  )
    #set($matchingOpp = $opp)
    #break
  #end
#end

## --- Output calculated date values ---
#if( $matchingOpp )
  ## Parse string to Date
  #set($baseDate = $convert.parseDate($matchingOpp.Pre_Auto_Renewal_Notification_Date__c, $ISO8601DateOnly, $defaultLocale, $defaultTimeZone))

  ## Convert to Calendar and add days
  #set($cal30 = $convert.toCalendar($baseDate))
  #set($cal90 = $convert.toCalendar($baseDate))
  #set( $void = $cal30.add($calConst.DATE, 30))
  #set( $void = $cal90.add($calConst.DATE, 90))

  ## Format and print output
 $date.format($CustomDateFormat, $cal90, $defaultLocale, $defaultTimeZone)##
#else
no date found##
#end