5.2 Personalization Syntax & Expression Editor
Key Takeaways
Simple profile attributes use Handlebars-style tokens such as {{profile.person.name.firstName}}.
Expression functions use PQL delimiters such as {%= upperCase(profile.person.name.firstName) %}.
Conditional blocks use {%#if ...%}, {%else if ...%}, {%else%}, and {%/if%}; equality is written with a single equals sign.
The fallback operator can supply a value, for example {%=profile.person.name.firstName ?: "there" %}.
Available profile, event, contextual, helper, and offer fields depend on the authoring surface and execution context.
5.2 Personalization Syntax and the Expression Editor
Journey Optimizer personalization is context-sensitive. Use the editor's selectors and documented syntax rather than borrowing tags from another template language.
Attribute insertion
A direct profile token uses Handlebars-style braces:
Hello {{profile.person.name.firstName}}
The field must exist in the profile schema exposed to the personalization editor and must be populated for the recipient. A valid path can still render blank if the value is null.
Event or contextual fields appear only when the message is authored in a context that exposes them. Use the field selector to insert the generated reference. Do not assume a generic variable such as xEvent exists in every message or fragment.
Expression functions
Functions and calculated expressions use PQL expression delimiters:
{%= upperCase(profile.person.name.firstName) %}
Use functions supplied in the editor for string, date, array, math, or other supported transformations. Function names and argument types matter. Preview the result with real representative values, particularly dates and locales.
Conditional blocks
The current conditional syntax is:
{%#if profile.loyalty.tier = "gold"%}
Gold member content
{%else if profile.loyalty.tier = "silver"%}
Silver member content
{%else%}
Standard content
{%/if%}
Important details:
- the opening tag is #if, not plain “if”;
- equality uses =, not JavaScript's “==”;
- the secondary branch is else if;
- the closing tag is /if, not “endif.”
The editor assists with validation, but semantic testing is still required. A condition can compile and still use the wrong enumerated value or capitalization.
Fallback values
Optional fields need a fallback. Journey Optimizer supports the fallback operator within an expression:
Hello {%=profile.person.name.firstName ?: "there" %}!
This is more reliable than an invented default() helper copied from another environment. A fallback should preserve grammar and brand tone. For a URL, the fallback should be a safe destination, not an empty string.
A field containing whitespace or malformed content may need additional data quality handling; do not assume every non-null value is usable.
Profile versus context
Profile data is durable customer state assembled in Real-Time Customer Profile. Context data belongs to the current execution, such as selected fields from a unitary or business event. A contextual value can be used only where the editor exposes it and is not automatically persisted to Profile.
Fragments add another boundary. Expression fragments can reuse personalization logic in supported content, but contextual attributes are not supported in all fragment workflows. Validate the fragment's type and the content surface before designing around event context.
Offer or decision fields are available only where the decisioning integration exposes them. Do not invent journey variables from a decision activity that the selected native channel cannot consume.
URLs and structured fields
Personalization in a URL is powerful and risky. Encode values appropriately, keep the scheme and host controlled, and avoid allowing a profile attribute to replace an entire trusted domain. Preview the final URL and test tracking.
JSON or custom-action payloads require valid escaping and datatypes. A string expression is not automatically a numeric value. Channel authoring and custom-action expression contexts should be treated separately.
Preview and proof matrix
Test at least:
- expected value;
- null or missing value;
- long value;
- non-Latin or accented value;
- each conditional branch;
- invalid or expired URL input;
- date near a time-zone boundary.
Preview with test profiles or sample data, then send proofs through the actual channel. A visual preview alone does not confirm inbox, device, or provider rendering.
Safety and governance
Availability in the selector does not authorize use. Sensitive, identity, and contract-labeled data can be restricted by policies. Avoid exposed personal data in subject lines, push lock screens, URLs, and analytics parameters. Confirm that marketing consent and purpose match the content.
Warning
The incorrect pattern “{% if ... %} ... {% endif %}” is not the documented Journey Optimizer conditional form. Use {%#if ...%} ... {%/if%} and test every branch.
Syntax practice
Create a compact test card with one direct token, one function expression, one fallback, and a three-branch conditional. Preview it with four profiles: Gold, Silver, another tier, and a missing tier/name. Copy the generated paths from the selector. If any result is blank or a tag appears literally, correct the expression before using it in a fragment or production message.
Keep a saved proof showing the exact test data and rendered result for each branch. When a schema path changes, rerun that proof rather than trusting an old screenshot or a successfully saved expression.
Which conditional opening and closing syntax is documented for Journey Optimizer personalization?
{% if condition %} ... {% endif %}
{{if condition}} ... {{endif}}
XML-style opening and closing if tags
{%#if condition%} ... {%/if%}
Which expression supplies a fallback first name?
{%=profile.person.name.firstName ?: "there" %}
{{ default(profile.person.name.firstName) }}
{% endif firstName %}
An XML-style fallback element containing there
Why use the field selector for event context?
It permanently writes every event field to Profile.
It shows which contextual fields are actually available in the current authoring surface and inserts the supported reference.
It bypasses governance labels.
It converts every context value into an offer.
Sections you finish are checked off in the contents.