Personalization syntax personalization-syntax
Personalization in Journey Optimizer uses two complementary syntaxes that work together in the same expression:
- Handlebars (
{{...}}) — used to render profile attributes, loop over arrays, and call block helpers. Refer to the HandlebarsJS documentation for a complete reference. - Profile Query Language (PQL) (
{%= ... %}) — used to call built-in functions (e.g.upperCase(),formatDate(),dateDiff()) and evaluate conditional expressions.
Understanding which context you are in is key to avoiding runtime errors. For example, a PQL function call placed inside {{...}} will fail because Handlebars tries to resolve it as a helper rather than evaluate it as a PQL expression.
Examples:
{{profile.person.name.firstName}}{%= upperCase(profile.person.name.firstName) %}{%#if profile.loyalty.tier = "gold"%}...{%/if%}{{#each profile.orders}}...{{/each}}The attributes structure is defined in an Adobe Experience Platform XDM Schema. Learn more.
Syntax general rules general-rules
-
Identifiers may be any unicode character except for the following special characters, which are reserved for the Handlebars syntax:
code language-none Whitespace ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~ -
The syntax is case sensitive.
-
The words true, false, null and undefined are only allowed in the first part of a path expression.
-
In Handlebars, the values returned by the {{expression}} are HTML-escaped. If the expression contains
&, then the returned HTML-escaped output is generated as&. If you do not want Handlebars to escape a value, use the “triple-stash”.Suppose the value of the field
profile.person.nameis “Mark & Mary”. The syntax{{profile.person.name}}will displayMark & Mary, while{{{profile.person.name}}}will showMark & Mary. -
Regarding literal functions arguments, the templating language parser does not support single unescaped backslash (
\) symbol. This character must be escaped with an additional backslash (\) symbol. Example:{%= regexGroup("abc@xyz.com","@(\\w+)", 1)%} -
To include a literal double quote inside a string value (e.g. when generating JSON output), escape it with a backslash (
\"):code language-handlebars { "message": "Hello \"{{profile.person.name.firstName}}\"" }Output:
{ "message": "Hello \"John\"" }Alternatively, use the triple-stash
{{{ }}}to output unescaped HTML when the value itself contains special characters you do not want HTML-encoded.
Reserved keywords reserved-keywords
Certain keywords are reserved in Profile Query Language (PQL) and cannot be used directly as field or variable names in personalization expressions. If your XDM schema contains fields with names that match reserved keywords, you must escape them using backticks (`) to reference them in your expressions.`
Reserved keywords include:
nextlastthis
Example:
If your profile schema has a field named next, you must wrap it in backticks:
{{profile.person.`next`.name}}
Without the backticks, the personalization editor will fail validation with an error.
{{...}} Handlebars paths and {%= ... %} PQL expressions, because these keywords are reserved at the path resolution level. This is different from hyphenated field names, where backtick escaping is only supported inside PQL expressions. See Hyphenated attribute keys.PQL syntax rules for special attribute keys pql-special-keys
Beyond reserved keywords, two additional cases require backtick escaping in PQL expressions.
Hyphenated attribute keys hyphenated-keys
If your XDM schema contains field names with hyphens (e.g. my-field, event-type) or names that start with or contain numbers, wrap the key in backticks inside PQL expressions:
{%= profile.events.`order-total` > 100 %}
{%= ... %}). It is not supported in Handlebars interpolation ({{...}}). However, hyphenated field names can be referenced directly in {{...}} blocks (e.g. {{profile.my-custom-field}}); only backtick syntax fails there.Without backticks in a PQL expression, the hyphen is interpreted as a subtraction operator and causes a PQL syntax error.
Numeric event IDs in context attributes numeric-event-ids
When referencing context event attributes where the event ID is a number (e.g. 1697323153), wrap it in backticks. This also applies inside functions like formatDate():
Type coercion type-coercion
PQL is strongly typed. When comparing or passing values, both sides must be the same type. Common cases:
stringToNumber() before arithmetic or comparison: {%= stringToNumber(profile.loyalty.pointsBalance) > 500 %}string_to_integer() or stringToNumber() before arithmetictoBool() to convert: {%= toBool(profile.consents.email.val) = true %}Available namespaces namespaces
-
Profile
This namespace allows you to reference all the attributes defined in the profile schema described in Adobe Experience Platform Data Model (XDM) documentation.
The attributes need to be defined in the schema before being referenced in a Journey Optimizer personalization block.
For more information how to leverage profile attributes in conditions, refer to this section.
accordion Sample references {{profile.person.name.fullName}}{{profile.person.name.firstName}}{{profile.person.gender}}{{profile.personalEmail.address}}{{profile.mobilePhone.number}}{{profile.homeAddress.city}}{{profile.faxPhone.number}}
-
Audience
To learn more about the segmentation service, refer to this documentation.
-
Offers
This namespace allows you to reference existing offers decisions.
To reference an offer you need to declare a path with the different information that define an offer. This path has the following structure:
offers.Type.[Placement Id].[Activity Id].Attributewhere:
offersidentifies the path expression belonging to offer namespaceTypedetermines the type of offer representation. Possible values are:image,htmlandtextPlacement IdandActivity Idare placement and activity identifiersAttributesare offer specific attributes which depend on the offer type. Example:deliveryUrlfor images
For more information on Decisions API and on Offer representations, refer to this page
All the references are validated against Offers Schema with a validation mechanism described on this page
accordion Sample references -
Location where the image is hosted:
offers.image.[offers:xcore:offer-placement:126f767d74b0da80].[xcore:offer-activity:125e2c6889798fd9].deliveryUrl -
Target URL when you click on the image:
offers.image.[offers:xcore:offer-placement:126f767d74b0da80].[xcore:offer-activity:125e2c6889798fd9].linkUrl -
Text content of the offer coming from the decisioning engine:
offers.text.[offers:xcore:offer-placement:126f767d74b0da80].[xcore:offer-activity:125e2c6889798fd9].content -
HTML content of the offer coming from the decisioning engine:
offers.html.[offers:xcore:offer-placement:126f767d74b0da80].[xcore:offer-activity:125e2c6889798fd9].content
Helpers helpers-all
A Handlebars helper is a simple identifier that may be followed by parameters. Each parameter is a Handlebars expression. These helpers can be accessed from any context in a template.
These block helpers are identified by a # preceding the helper name and require a matching closing /, of the same name.
Blocks are expressions that have a block opening ({{# }}) and closing ({{/}}).
For more information on helper functions, refer to this section.
Literal types literal-types
Adobe Journey Optimizer supports the following literal types:
Examples:
"prospect", "jobs", "articles"Examples:
-201, 0, 412Note: You cannot directly access properties of items within an array.
Examples:
[1, 4, 7], ["US", "FR"]Best practices best-practices
Review these syntax rules before building personalization expressions. Most runtime errors come from mixing up Handlebars and PQL contexts.
Use the correct conditional block syntax
Always use {%#if%} / {%else if%} / {%else%} / {%/if%}. The {% if %} / {% elseif %} / {% endif %} syntax is not supported.
Do not call PQL functions inside {{...}} Handlebars blocks
{{...}} resolves Handlebars variables and helpers only — it does not evaluate PQL. Wrapping a PQL function like upperCase() inside {{...}} causes a “could not find helper” error. Use {%= ... %} instead:
{{upperCase(cleanName)}}{%= upperCase(cleanName) %}Use a named loop alias when combining {{#each}} with {%#if%}
this.field is resolved by the Handlebars renderer but not by the PQL evaluator inside a {%#if%} condition. Define a named alias with as |item| so both contexts can resolve the field:
Assign PQL function results to a variable before looping
PQL UDFs such as topN cannot be called directly inside {{#each}}. Evaluate them first with {% let %}, then iterate over the result:
Use {% let %} to avoid repeating function calls
When a computed value is needed more than once, store it in a variable. This improves readability and prevents redundant evaluations:
Use the correct argument order for dateDiff
dateDiff(start, end) takes the earlier date first. To compute days remaining until a future date, pass the current date as the first argument:
Use = for equality comparisons in PQL, not ==
PQL uses a single = operator for equality. Using == results in a syntax error.
Use backticks for hyphenated field names — in PQL expressions only
If an XDM schema field name contains a hyphen (e.g. order-total), wrap it in backticks to prevent the hyphen from being parsed as a subtraction operator. This is only supported inside {%= ... %} PQL expressions, not in {{...}} Handlebars blocks:
{%= profile.events.`order-total` > 100 %}
For ready-to-use expressions you can copy directly into your content, see Personalization recipes.