Personyze actions — banners, popups, recommendation widgets, emails, and more — are normally designed visually in the campaign editor. But behind every visual editor is HTML source code with Personyze’s templating directives, and you can edit that HTML directly when you need finer control. This article is the complete reference for the template language.
The language is plain HTML and CSS, with a few additions:
- Preprocessing directives wrapped in
${...}— variables, conditionals, loops, and functions evaluated server-side before the HTML is sent to the page. - Special class names (prefixed with
$) that turn ordinary elements into Personyze-aware widgets — buttons, popups, switchable areas, conditional forms, and more. - Menu directives at the bottom of the template that define the visual editor controls a non-developer sees in the GUI.
Preprocessing directives
A dollar sign followed by braces designates a preprocessing directive. The contents of the braces are evaluated and substituted with the result before the HTML is delivered to the browser. Inside the braces you can use a variable, a function call on a variable, or any of the structural directives covered below.
${some_variable}
The directive can produce text that itself contains another directive — Personyze evaluates in two passes:
- Preprocess pass — substitutes
${args->...}argument variables into the template. - User pass — substitutes per-user variables (
${first_name},${country_code}, etc.) for each individual visitor.
Preprocess-time variables (args)
To create a preprocess-time variable — the kind that appears as an editable field in the visual GUI — declare it once at the bottom of the template with a ${menu} directive, and reference it anywhere in the template body with ${args->name}:
<p title="${args->main_text:html}">
${args->main_text}
</p>
${menu args->main_text name=\'Main text\'}
Now the GUI shows an editable “Main text” field. Whatever the user types there is inserted both inside the <p> tag and into its title attribute (with HTML special characters escaped via the :html conversion so the value can’t break out of the attribute and inject extra tags).
Argument expression syntax
The general form is the args keyword, an arrow, the variable name, then zero or more colon-prefixed conversions:
${args->variable_name:function1:function2(parameter)}
User profile variables
The second processing pass substitutes per-user values. Personyze ships with these built-in profile variables:
first_namelast_nameemailindustry
You can also define custom profile fields and collect values for them via forms or the API. Once defined, they’re available in the same way:
${first_name:default('User')}
Preprocess-time variables can produce text that contains second-pass user variables. Use double-quoted apostrophes for nested defaults:
<p>
${args->main_text:default(\'Dear, ${first_name:default(\'\'User\'\')}\')}
</p>
${menu args->main_text name=\'Main text\'}
Session and page variables
Variables that depend on the visitor’s current browsing session and device:
| Variable | Returns |
|---|---|
url_host_port |
Site domain name. |
url_path |
URL path (part after the domain name). |
ur |
The whole URL of the current page. |
country_code |
Two-letter country code (e.g. US, DE). |
city |
City name. |
lang_0 |
Primary browser language. |
known_device_type |
regular (desktop), tablet, or phone. |
is_returning_user |
0 for new users, 1 for returning. |
landing_url |
URL of the landing page that started this session. |
landing_host_port |
Domain part of the landing page. |
landing_path |
URL path part of the landing page. |
part_of_day() |
morning, afternoon, evening, or night. |
weekday |
Day of week as number: 0 = Sunday, 1 = Monday, …, 6 = Saturday. |
day |
Day of month. |
last_month_day |
Last day in current month — 28, 29, 30, or 31. |
Functions
Functions are applied to variables with colon-prefix syntax:
${variable:func_name}
${variable:func_name(parameter)}
${variable:func_name(\'parameter\')}
${variable:func_name(\'param1\', \'param2\', \'param3\')}
If a parameter contains commas, apostrophes, or closing parentheses — or if there’s more than one parameter — it must be quoted. To represent an apostrophe inside a quoted string, double it: :func_name('I''m a string').
String & HTML
| Function | Description |
|---|---|
html |
Escape HTML-special characters: &→&, "→", <→<. Always use this on values inserted into attributes or anywhere user-supplied content shouldn’t be parsed as markup. |
default |
For args variables: specify the current/initial value. For user-profile variables: specify a fallback if the variable is empty. With args, place it on only one occurrence even if the variable appears multiple times. |
json |
JSON-stringify the value. Useful for safely embedding arbitrary data in JavaScript: onclick="alert(${p->title:json:html})". |
url |
URL-encode (e.g. ? → %3F). |
lc |
Lowercase: ${p->title:lc}. |
uc |
Uppercase. |
replace |
Replace all occurrences of one substring with another: ${url:replace('http://', 'https://')}. |
regexp_replace |
Replace by regular expression. |
ellipsis |
Truncate to first N characters and append … if anything was cut: ${p->description_long:ellipsis(50)}. |
left |
First N characters (no ellipsis). |
right |
Last N characters. |
substr |
Starting at character N, extract M characters. |
substring |
Substring from character N to character M (exclusive). |
text_after |
Text after a specified word or substring. |
text_before |
Text before a specified word or substring. |
Numeric & price formatting
| Function | Description |
|---|---|
sprintf |
Format a number with placeholders. %s for raw, %f as number, or %[#,###.##]f for full thousands/decimal control. # is an optional digit, 0 is mandatory. Use g instead of f to omit trailing zeros for whole numbers. Arabic digits via ٠ as the placeholder. |
price |
Format as price using your account’s number format. Two predefined formats: :price for default, :price('alternative') for the second. The GUI offers a control to set both. |
round |
Round to nearest. |
floor |
Round towards negative infinity. |
ceil |
Round towards positive infinity. |
truncate |
Drop the fractional part. |
Iterating Personyze tables (${foreach})
Personyze tables — your product catalog, article catalog, computed user interests — are iterated with ${foreach ... as alias where ... limit N} ... ${end}:
${foreach products as p where p->price_after_discount < p->price_before_discount limit 5}
<div>
${p->title}
</div>
${end}
Recommendation widgets use this pattern to render the actual product/content cards. The recommendation algorithm chosen in the GUI determines the table name and filters that appear in the template. Example for “best sellers in the recent window”:
${foreach sum_products as p where p->for_period=\'recently\' and p->n_goal>0 order by p->n_goal desc limit 12}
...
${end}
You can have multiple ${foreach} loops in a template, including nested ones. If one of them has the alias main, the GUI’s recommendation-algorithm selector controls that loop. Otherwise it controls the first found.
Currently-viewing product
If a product-view event is reported on the current page, you can access details of the currently-viewing product directly via viewing_product — no ${foreach} needed:
You're interested in ${viewing_product->color} color.
Other automatically-available single-row tables:
viewing_product— currently viewing product (if a view event was reported on this page).last_viewed_product— most recently viewed product across the visitor’s session.last_extra_product— most recently added-to-cart product.
Conditional directives
${if} / ${else if} / ${else}
Include parts of the template based on a preprocess-time variable, profile variable, or table column:
${foreach products as p where p->price_after_discount < p->price_before_discount limit 5}
<div>
${p->title}
</div>
<div>
${if p->price_after_discount < p->price_before_discount}
${p->price_after_discount}
<strike>${p->price_before_discount}</strike>
${else if p->price_after_discount < 10}
Cheaper than $10.
${else}
${p->price_after_discount}
${end}
</div>
${end}
Conditional expressions can reference args, allowing user-controllable filters:
${foreach products as p limit 5}
${if p->price_after_discount > args->min_price}
<div>${p->title}</div>
${end}
${end}
${menu args->min_price name=\'Show products not cheaper than\', type=\'number\'}
${case}
Two forms — boolean (each when is its own condition) and expression (each when compares against the case expression):
${case}
${when known_device_type = \'phone\'}
On phone
${when known_device_type = \'regular\'}
On desktop
${else}
Not known
${end}
${case known_device_type}
${when \'phone\'}
On phone
${when \'regular\'}
On desktop
${else}
Not known
${end}
Conditional blocks (toggleable in the GUI)
Conditional blocks are HTML elements marked with ${block->block_name}. The GUI shows a toggle that enables or disables the block — useful for “with button / without button” style options.
${block->with_button:default(0)}
<div>
<button type="button">See more</button>
</div>
${menu block->with_button name=\'With button\'}
HTML structure & by_id
Templates can contain any HTML — including <style> tags. A few transformations are applied:
id attributes are stripped — but accessible via by_id
If your template has elements with id attributes, those attributes are removed before rendering (to avoid collisions with other markup on the page). But you can still reference those elements via a special by_id object available in onclick handlers and a special ontplready attribute:
<div style="background-color:transparent; display:flex; align-items:center; gap:0.3em">
<img id="main_icon" src="https://www.google.com/favicon.ico">
<button type="button" onclick="by_id.display.textContent = by_id.main_icon.width">Get icon width</button>
<div id="display"></div>
</div>
ontplready — script when template is mounted
Any element can have an ontplready attribute containing JavaScript that runs once the template is inserted into the document. Inside it, this refers to that element. Use it to bind helper functions to the root element rather than polluting the global scope:
<div id="root" ontplready="this._hide_message = () => by_id.message.style.display = \'none\'">
<div id="message">Text text text.</div>
<button type="button" onclick="by_id.root._hide_message()">Hide</button>
</div>
ontplready instead, and access them through by_id. When using querySelector(), scope it to the template root: by_id.root.querySelector(...) rather than document.querySelector(...).Special class names
Class names prefixed with $ turn an element into a Personyze-aware widget. The $ class is removed from the rendered HTML.
Recognized special classes: $responsive, $btn, $flat_btn, $personyze_button_dont_show_again, $a_add_params, $hint, $icon, $popup, $switch-elem, $cform, $info_lines_scroller, $rating, $table_slider, $table_slider_pagination, $button_add_to_cart.
$responsive
Usually applied to the template root. Enables a data-style attribute that supports a special sel(option1, option2) CSS function — Personyze picks whichever option causes less overflow on the visitor’s device.
<div class="$responsive" data-style="background-color:transparent">
<div data-style="max-width: sel(500px, 80%)">
...
</div>
</div>
If a popup template is $responsive but still overflows, Personyze applies zoom to scale it down. Regular style attributes and <style> tags work alongside data-style.
$btn
Turns the element into a Personyze-styled button. Use data-icon="..." for an icon before the text, or data-icon_append="..." for after. Icon names come from Font Awesome 4.
<a class="$btn" data-icon="bullhorn" href="javascript:" onclick="alert(this.dataset.message)" data-message="${args->message:html:default(Hello)}" style="text-decoration:none; padding:0.1em 0.3em">
Show message
</a>
$personyze_button_dont_show_again
Close button that suppresses the action for the configured number of sessions. The data-action_id and data-n_sessions attributes wire it up:
<img src="https://counter.personyze.com/images/close-buttons/black-16x16.png"
style="width:16px; height:16px"
class="$personyze_button_dont_show_again"
data-action_id="${action_id}"
data-n_sessions="1">
Usually paired with a ${menu} item of type='dontshowagain' so the close-button’s appearance is editable in the GUI.
$a_add_params
Adds URL parameters to all <a> links in the template. Useful for ad-tracking. Specify the parameters in data-json-params:
<div class="$responsive $a_add_params" data-json-params="${args->url_params:html}">
...
</div>
${menu name=\'Frame\', icon=\'th-large\'}
${menu args->url_params name=\'URL additional parameters\', type=\'external_media_url_params\'}
$hint
Shows a tooltip on hover. Tooltip text comes from title="...", or — for HTML content — from a nested element marked data-content="1":
<a href="https://example.com/" class="$hint" data-popup_style="background-color:gold; color:purple; padding:1em">
<span data-content="1">
This is <b>Example</b> site.
</span>
Link
</a>
$icon
Renders a Font Awesome 4 icon. Icon name is in the class attribute alongside $icon:
<i class="$icon envelope"></i>
<i class="$icon ${args->icon_name:html:default(envelope)}" style="font-size:125%; color:crimson"></i>
${menu args->icon_name name=\'Icon\', type=\'icon\'}
$popup
Show a popup when the user clicks (or hovers) a trigger element. The popup references its trigger by id:
<div>
<a href="javascript:" id="trigger_1">More info</a>
<div id="popup_1" class="$popup" data-trigger_id="trigger_1" data-is_modal="1"
data-json-trigger_transit=\'{{"template": "animator_fade"}}\'
style="max-width:10em; border:solid 1px; border-radius:5px; background-color:lightgreen; padding:1em">
Blah blah blah blah blah blah blah.
<div>
<a href="javascript:" onclick="by_id.popup_1._hide()">Close</a>
</div>
</div>
</div>
Attributes that control behavior: data-trigger_id, data-trigger_onmouseover="1", data-trigger_onfocus="1" (for input fields), data-is_modal="1", data-json-trigger_transit for animation (animator_fade, animator_swap_top).
$switch-elem
An area that can switch between alternate cases (e.g. a multi-step modal). Each child has data-case="case_name"; only one is mounted at a time:
<div id="sw" class="$switch-elem" data-case="step-a" style="position:relative; overflow:hidden">
<div data-case="step-a">
<p>Step A.</p>
<button class="$btn" onclick="by_id.sw._set_case(\'step-b\', null, false, {{template: \'animator_swap_bottom\'}})">Next</button>
</div>
<div data-case="step-b">
<p>Step B.</p>
<button class="$btn" onclick="by_id.sw._set_case(\'step-a\')">Previous</button>
</div>
</div>
JavaScript methods on the switch: _set_case(name) (or null to hide all), _get_case(), _get_cases().
$cform
A form that submits data back to Personyze for visitor profile enrichment, lead capture, or campaign-attached form actions. The form name lives in the class attribute alongside $cform:
<div class="$cform form1" ontplsubmit="personyze.push([\'Submit\', \'${action_id:html}\', this._get_value_ex(), arguments[1], arguments[2]]); return true" ontplaftersubmit="alert(\'Success\')">
<input form="form1" name="Email" type="email" required="1" data-prop-column="email" value="${email:html}">
<button form="form1" type="submit">Submit</button>
</div>
Form attributes
data-required_field_message="..."— message shown next to a missing required field.data-invalid_value_message="..."— message shown next to an invalid input.ontplchange="..."— JS that runs on every value change.ontplsubmit="..."— JS that runs on submit. Must callarguments[1]()on success orarguments[2](error)on failure. Submit to Personyze with:personyze.push(['Submit', '${action_id:html}', this._get_value_ex(), arguments[1], arguments[2]]); return true.ontplaftersubmit="..."— JS that runs after successful submission.
Form input attributes
Inputs belonging to the form must have form="form_name" and name="...". Optional attributes:
required="1"— input must be filled to submit.pattern="..."— regex validation.data-valid_if="..."— JS expression that returns truthy/falsy. Returning a string shows that string as the error message.data-prop-prop_name="..."— input properties surfaced viaform._get_value_ex(). The special propertycolumnmaps the input to a Personyze user-profile field.data-use_hidden="1"— include this input even when hidden by a form-case condition.data-value_if_hidden="..."— fixed value to use when the field is hidden.
Form-case visibility
Wrap optional input groups in a container with data-form_case="..." to show/hide them based on other form values:
<div class="$cform form1">
<input form="form1" name="ContactMe" type="checkbox">
<div form="form1" data-form_case="v.ContactMe">
Phone: <input form="form1" name="Phone" type="tel">
</div>
<button form="form1" type="submit">Submit</button>
</div>
JavaScript methods on the form element
_get_value()— returns a flat object of input names → values._get_value_ex()— returns input names → objects withvalueplus alldata-prop-*properties._set_value(new_value)— sets values from an object.
Menu items (visual editor controls)
The ${menu} directives at the bottom of the template define what the GUI editor shows. There are two types:
Section directives
${menu name='Section name', icon='icon-name'}
Parameters: name (label) and optional icon (Font Awesome 4 name — common ones: image, envelope, user, check, times-circle, edit, font).
Item directives
${menu args->arg_name name=\'Argument name\', description=\'...\'}
${menu block->block_name name=\'Block name\', description=\'...\'}
${menu table_alias->column_name name=\'Column name\', description=\'...\'}
Three forms — for editing an argument value, for toggling a conditional block, or for letting the user pick a different column from a table.
Combined example
${menu name=\'Text\', icon=\'font\'}
${menu args->headline name=\'Headline Text\'}
${menu args->headline_style name=\'Headline Style\', type=\'css\'}
${menu name=\'Close Button\', icon=\'times-circle\'}
${menu args->close_button_style name=\'Close Button Style\', type=\'css\'}
${menu args->close_button name=\'Close Button HTML\', type=\'dontshowagain\'}
The default editor for an argument is HTML or plain text (depending on whether the argument has the :html conversion). Use type='...' to choose a specialized editor — see below.
Field editor types
Most editor types accept extra parameters via param='key=value' notation in the menu item.
type='number'
Numeric input. Parameters: min, max, placeholder.
${menu args->n_columns name='Number of columns', type='number', param='min=2', param='max=10', param='placeholder=Columns'}
type='checkbox'
Boolean toggle. The inserted value can be customized: value_on (default 1) and value_off (default 0).
<input name="email" ${args->email_required}>
${menu args->email_required name=\'Required Field\', type=\'checkbox\', param=\'value_on=required="1"\', param=\'value_off=style="opacity:0.5"\'}
type='select'
Drop-down. Options as a JSON array of [label, value] pairs:
${menu args->img_position name='Image Position', type='select', param='options=[["On Top","top"],["On Bottom","bottom"],["On Right Side","right"]]'}
type='textarea'
Multi-line text. Parameters: placeholder, autosize (1 to grow with content), autosize_max_rows.
type='css'
Visual CSS editor. Output is a style attribute string. Add param='with_responsive=1' to produce data-style-friendly output with sel() functions for responsive templates.
${menu args->close_button_style name='Close Button Style', type='css', param='with_responsive=1'}
type='color'
Color picker. Inserts a CSS color value:
<div style="background-color: ${args->color}">
...
</div>
${menu args->color name=\'Background color\', type=\'color\'}
type='dontshowagain'
Specialized editor for close-button HTML. Pairs with the $personyze_button_dont_show_again class.
type='icon'
Icon picker (Font Awesome 4 names).
type='external_media_url_params'
Editor for the URL parameters appended by $a_add_params.
Ready-made examples on GitHub
Personyze maintains a library of fully-annotated example templates covering common patterns. They live alongside this reference at github.com/personyze/personyze-template-language:
Related guides
- Developer Resources Hub — REST API and SDK overview.
- JavaScript Action Guide — for actions where you write JavaScript directly instead of using templates.
- Popup & Banner Action Guide — visual-editor walkthrough that complements this reference.
- Content Variations — using conditionals at the audience-targeting level (vs. at the template level).
- Dynamic CRM Variables in Content — inserting CRM data via the same
${variable}syntax.