Personyze Wiki Personyze Wiki docs
Open Personyze
Developer Resources

Template Language — Action HTML Reference

Complete reference for the Personyze action template language: ${args} variables, user/session variables, functions, ${foreach} loops over Personyze tables, conditionals, special $-prefixed classes ($responsive, $btn, $popup, $cform, etc.), form handling, and…

Updated 8 hours ago 16 min read
A
by Admin

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.

Examples vs. reference.This is the syntax reference. For ready-to-use template examples covering popups, forms, countdown timers, recommendation widgets, dynamic emails, and social proof, see the template language repo on GitHub — 12 fully-annotated example files. We can pull individual examples into the wiki on request.

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:

  1. Preprocess pass — substitutes ${args->...} argument variables into the template.
  2. 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_name
  • last_name
  • email
  • industry

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: &&amp;, "&quot;, <&lt;. 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>

Stay scoped to the template.Avoid creating global variables or functions from template JavaScript. Bind helpers to elements via 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 call arguments[1]() on success or arguments[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 via form._get_value_ex(). The special property column maps 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 with value plus all data-prop-* properties.
  • _set_value(new_value) — sets values from an object.

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:

📷 Popup, Banner & HTML BuilderImage-based action templates. Open →
📝 Lead Capture FormForm-based lead-gen actions with validation. Open →
Countdown TimerTimer widgets with end-date logic. Open →
🛒 Product RecommendationsGrid/carousel/list layouts for product widgets. Open →
📰 Content RecommendationsArticle/blog recommendation widgets. Open →
🗂 Category RecommenderCategory-level recommendation surfaces. Open →
📧 Dynamic / Remarketing EmailOpen-time email content templates. Open →
📦 Email — ProductsProduct recommendations inside emails. Open →
📋 Form with RecommendationsLead form combined with personalized product recs. Open →
🌐 External Media — ArticlesEmbed external article content in widgets. Open →
🛍 External Media — ProductsEmbed external product feeds in widgets. Open →
Social Proof Widget“Recently purchased” / last-goal-user surfaces. Open →

Native imports on demand.Each example file is heavily annotated source — copy-paste-ready, but huge (50KB-228KB each). On request, I can pull any specific example into a native wiki article with cleanup, screenshots, and additional context.