Liquid personalisation allows you to use Liquid markup language to create targeted and relevant content both on landing pages and in campaigns.
In addition to other means of personalisation, Liquid can be used to:
Pull Insight data into a campaign.
Pull Insight data into a landing page.
Loop through collections of data to repeat blocks of HTML to each relevant piece of content.
Manipulate the way text, dates, currency and numbers are displayed.
Show content depending on list membership.
Use complex logic to determine what content should be displayed to each contact.
What is Liquid?
Liquid is a markup language, which was developed for the ecommerce system Shopify. Liquid allows you to create content that changes, depending on the current data, and acts as an extension to the CSS and HTML that emails and landing pages are built upon.
We use Liquid because many front end developers are used to working with it, or find it easy to learn it. A fuller description of what is possible in Liquid can be found on GitHub.
We support an extensive subset of the Liquid markup language. However, not all elements are supported.
Before you start
You must have Liquid enabled for your account.
There are reserved variable names.
Don't use the following variable names, as they're reserved for system-defined values:
account
contact
token
campaign
Ensure you read the section HTML reserved characters below. Certain characters are altered when your content is saved, which can change what your logic does without showing an error.
The basic elements of Liquid personalisation
Data
What information you have about contacts.
Logic
Why contacts should/should not see certain content and what content those contacts should/should not see, depending on their data.
Content
Outputs content to those contacts, using HTML, CSS, outputs and filtered outputs.
Output data into content
You can still use the data object contact.addressbooks. This object also refers to Lists. Address books was the previous name for Lists in Dotdigital Marketing.
Outputs can be used to take data and create content.
For example:
Hello {{ contact.data.firstname }},
This outputs 'Hello' followed by the first name stored for a particular contact.
This is exactly equivalent to the below using contact data field personalisation:
Hello @FIRSTNAME@,
In this case we outputted a piece of contact data; we can also output account-specific data. For example:
Hello {{ contact.data.firstname }}, the current time is {{ account.datetime }}.
This shows a message to the named contact containing the current date and time.
Filtered outputs
You can use filters to change the way outputs are displayed. For example:
Hello {{ contact.data.firstname }}, the current date is {{ account.datetime}}.
Might be outputted as:
Hello ben, the current date is 05/08/2015 07:30:31.
But it can be changed with filters as follows:
Hello {{ contact.data.firstname | capitalize }}, the current date is
{{ account.datetime | date: "%e %B %Y" }}.To show:
Hello Ben, the current date is 05 August 2015.
The first element in the output (surrounded by {{}} brackets ) is always what gets outputted, and the other bits (separated by | pipes) are filters.
An output can have multiple filters. For example:
{{ account.datetime | date: "%e %B %Y" | upcase }}
outputs the date in the format containing the day (dd), month and year, in uppercase:
05 AUGUST 2015
Logic tags
Tags are used when building logic to tell your content what to do, for example:
<!--{% if contact.data.firstname == 'ben' %}-->Hey Ben!
<!--{% else %}-->Hello there!<!--{% endif %}-->This example shows different content depending on whether or not the contact's first name is Ben.
A more interesting example is using for-loops, for example:
{% for list in contact.lists %}{{ list.name }} <br/>{% endfor %}
returns the lists that a contact is in, and for each list shows its name followed by a line break.
Even with these basic rules, Liquid personalisation allows for some truly tailored content, but the power really comes when you combine all of the available options.
Where Liquid can and can't be used
Location | Liquid supported? | Notes |
EasyEditor – Liquid block | Yes | Full Liquid logic, loops, filters, and outputs supported. |
EasyEditor – standard text blocks | Partial | Simple outputs work; complex logic may not render. |
SMS editor | Yes | Full Liquid logic, loops, filters, and outputs supported. |
Subject lines | No | Liquid is not processed in subject lines. |
Dynamic Content blocks | No | Liquid can't run inside Dynamic Content rules. |
External Dynamic Content | No | Liquid is not evaluated inside External Dynamic Content output. |
HTML reserved characters
Liquid blocks in EasyEditor are saved as HTML content. This means that if you type a literal < or > character, it's automatically converted to its HTML entity equivalent, < or >, when you save, exactly as a browser would encode those characters in any other HTML content. This is standard, expected behaviour and not something EasyEditor can bypass.
Why this matters
Liquid's parser doesn't decode HTML entities before evaluating your code. So, if a comparison operator like <, >, <=, or >= gets silently converted to its entity form, Liquid no longer sees a valid operator.
This does not cause a visible error. An {% if %} condition with a broken operator falls back to evaluating as always-true, meaning the true branch of your logic runs regardless of the actual comparison. This means that your content that looks correct at a glance but is logically wrong. You must always check any conditional logic using these operators carefully after saving.
Affected characters and tags
Three characters are affected: <, > and &. This includes the operators <, >, <= and >=.
To avoid problems with your logic, wrap the whole logic block in HTML comments, not just the tag that contains the operator. That means the opening tag and every tag that belongs with it — {% elseif %}, {% else %}, {% endif %}, {% when %}, {% endcase %} and so on — even though those closing tags don't themselves contain a reserved character. The tags must be wrapped consistently for the block to render correctly.
Ampersands
The & character is also converted, to &, which actually alters your data. If you write:
{% assign brand = "Tom & Jerry" %}
the value actually stored is Tom & Jerry.
This means:
Comparisons stop matching
The logical statement
{% if contact.data.company == "Tom & Jerry" %}will never match a contact whose company actually is Tom & Jerry, because the two strings are no longer identical. Again, you don’t see an error; the condition simply takes the wrong branch.
String filters return wrong results
size,truncateandsliceall count the entity characters. truncate can also cut through the middle of an entity, leaving fragments such as&amvisible in your content.
URLs can break
A query string like?utm_source=email&utm_medium=campaigncontains&within it. Applyingurl_encodeto it produces%26amp%3B, which doesn’t work as a link.
Writing & yourself instead of & doesn’t avoid this; it’s decoded and re-encoded back to the same result. Wrapping the tag in an HTML comment is the only way to store a literal ampersand.
How to prevent issues
To prevent the above problems, wrap all Liquid tags containing these characters, including any nested {% elseif %}, {% else %}, {% case %}, and {% when %} tags, in HTML comments: <!-- -->.
Comments aren't encoded by the editor, so the characters inside are preserved exactly as typed.
<!--{% if contact.data.age > 18 %}-->Access granted<!--{% else %}-->Access denied<!--{% endif %}-->
Best practice tip
Because it's easy to add a comparison operator later and forget to update the wrapping, it's sensible to wrap all logic tags in HTML comments by default, whether or not they currently contain a reserved character.
Where in your HTML the Liquid sits
You might notice that some Liquid appears to work even without being wrapped. This can depend on where in the HTML your Liquid script is placed:
Where the Liquid is | What happens | What you see |
Inside an HTML comment | Nothing is converted. Everything works. | Your Liquid logic and data are preserved correctly. |
In ordinary content |
| Nothing. Wrong content sends with no error. |
Inside a quoted HTML attribute value, for example |
| Partly works, which is misleading. |
Inside an HTML tag but outside the quotes, for example | The | A visible Liquid error. |
If you need conditional styling, work the value out inside a comment and then output it:
<!--{% if contact.data.age > 18 %}{% assign rowStyle = "color:green" %}
{% else %}{% assign rowStyle = "color:red" %}{% endif %}-->
<td style="{{ rowStyle }}">Your content</td>Liquid markup is space sensitive
You must make sure that you separate each operator with a single space. For example, the following code evaluates to true:
{% if 1 == 1 %}
Whereas, the following code evaluates to false:
{% if 1==1 %}
As with the issue caused by the conversion of reserved characters, no error is shown. A condition with missing spaces always evaluates as false, so the false branch runs for every contact. You must ensure you test with contacts who should and shouldn't see the content.
Check your content
After saving, reopen the code block or source code view and look for <, >, or & anywhere you originally typed <, > or &. If you see the entity instead of the character, that tag wasn't properly wrapped in a comment and needs fixing.
Always confirm conditional logic with a real test send as well as a preview, and check both outcomes of each condition, for example, send to a test contact who should see the content and one who should not. Because a broken condition behaves as always-true, testing only with a contact who should see the content won’t reveal the problem.
Because browsers and email clients decode & back to & when displaying content, an affected string looks completely correct in preview and in a test send. The only reliable way to detect it is to check the length. If you suspect a problem, temporarily output:
{% assign test = "Tom & Jerry" %}{{ test | size }}
A result of 11 means the ampersand was preserved; 15 means it was converted and the string is corrupted.
