What are Merge Tags?
Transaction-specific data/code that you can insert into your HTML Email and Ticket Templates.
This could be data such as Event Name, Customer Name, Ticket price, etc.
Learn more - Full List of Merge Tags
What are the different kinds of Merge Tags I can use?
There are different kinds of Tags depending on the data that you want to include:
Global Tags
These are singular values that are unrelated to a specific item in a transaction, but related to the transaction as a whole, e.g:
Organisation name
{{ ORGANISATION.NAME }}
Customer name
{{ CUSTOMER.FIRST_NAME }}
{{ CUSTOMER.LAST_NAME }}
Total value of transaction
{{ "{:,.2f}".format(TRANSACTION.GROSS) }}
Loop Tags
These are values that tend to be related to a specific item in a transaction, e.g.:
Event Name
{{ ticket.EVENT.NAME }}
Event Venue
{{ ticket.EVENT.VENUE_NAME }}
{{ ticket.EVENT.VENUE_ADDRESS }}
Ticket price for event
{{ "{:,.2f}".format(ticket.GROSS) }}
Loop tags need to be placed into a Loop in your HTML for it to work properly.
Learn more - Guide: Add Merge Tags to Templates
Conditionals
These are Tags that are conditional depending on what has been ordered, e.g.:
Discount value - if a discount has been applied
External price adjusters (external only - not internal)
{% if adjuster.EXTERNAL %}
{{ adjuster.NAME }}
{% endif %}
These can be achieved by using 'if' statements on your HTML.
FAQs
Why isn't my Merge Tag working?
Why isn't my Merge Tag working?
This may be because Loop tags are being placed outside of Loops, or there's an incorrectly formatted Merge Tag/bit of HTML code.
NOTE - As the ticketing software provider, we deliver and maintain the ticketing system. We do not provide template design and as such we would suggest going to an external HTML designer for detailed help with HTML templates.
We recommend unlayer for designing HTML templates.
ChatGPT is also very helpful for specific HTML queries.
Why does nothing happen when I Submit my Template?
Why does nothing happen when I Submit my Template?
If Submit appears to do nothing, the Template has not passed our checks and has not been saved. The most common cause is a groupby on a list built inside the Template (see Is there a way to group tickets together in a template? below).
Go back to the last version that saved and re-add your changes one at a time to find the one that stops it saving, or contact Support with the Template name.
How do I stop Event information appearing for every Ticket in my template?
How do I stop Event information appearing for every Ticket in my template?
By adding an if loop.first statement, followed by the Event Merge Tags you don't want to appear for every ticket.
Example:
{% for ticket in TRANSACTION.TICKETS %}
{% if loop.first %}
{{ ticket.EVENT.VENUE_NAME }}
{{ ticket.EVENT.VENUE_ADDRESS }}
{% endif %}
{% endfor %}
This ensures that Venue Name and Address only appears once/for the first ticket.
You'd then need to remove {{ ticket.EVENT.VENUE_NAME }} and {{ ticket.EVENT.VENUE_ADDRESS }} from the Event information in your template.
How do I exclude returned items on my Emails/Tickets?
How do I exclude returned items on my Emails/Tickets?
By adding this bit of HTML before your Event/Ticket information:
{% for ticket in TRANSACTION.TICKETS | selectattr("TRANSACTION_TYPE", "equalto", "SALE") %}
You just have to make sure to close the loop with an {% endfor %} Merge Tag.
Here's an example whereby you'd only want the customer to see the Performance Date/Time for their new tickets, not the old tickets they exchanged:
{% for ticket in TRANSACTION.TICKETS | selectattr("TRANSACTION_TYPE", "equalto", "SALE") %}
{{ ticket.PERFORMANCE_START_TIME.strftime('%I:%M %p') }} ON {{ ticket.PERFORMANCE_START_DATE.strftime('%a %d %b') }}
{% endfor %}
NOTE - be careful using this filter in a Template that may receive Exchanges. Your Transaction total comes from {{ TRANSACTION.GROSS }}, which already nets off the returns, so if the returned items are hidden the item lines will no longer add up to the total. Filtering to sales is right for a sales-only Template, or inside an if statement that decides what to say. For Exchanges, group the items by TRANSACTION_TYPE instead, so returned items show on their own labelled line (see Is there a way to group tickets together in a template? below).
How do I add the total number of Tickets in a Transaction?
How do I add the total number of Tickets in a Transaction?
By adding this bit of code, then printing the value wherever you need it:
{% set count = TRANSACTION.TICKETS|length %}
{{ count }}
Can I add my own Organisation logo instead?
Can I add my own Organisation logo instead?
Yes, you just need to change img src from the logo URL:
To the {{ ORGANISATION.IMAGE }} Merge Tag :
This will mean that the Organisation image from your Settings page will pull through onto the Email Template instead of the Line-Up logo.
NOTE - if you are using your Org image in your template, this Merge Tag is safer to use than a hosting link. This is because some email servers may treat external hosting links as cold domains and may block the emails from being received.
Here's a video of these steps:
How do I group tickets together and show "Quantity"?
How do I group tickets together and show "Quantity"?
By adding a "Qty" column in your Items table and using our {{ count }} Merge Tag. Below are the steps to add this to your Items table in your Template.
NOTE - this will group tickets together by Price Variant. Because of this, it works best for GA events, where there is no Seat Label for your tickets.
Add the Qty header cell in the table header row (after the Item cell, before Price):
<td
width="10%"
align="left"
bgcolor="#eeeeee"
style="
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 24px;
padding: 10px;
text-align: right;
" >
Qty
</td>Adjust the Item column from
width="65%"towidth="55%"and Price fromwidth="35%"towidth="25%"in the header row, to make room.Add the namespace and deduplication logic at the top of the ticket loop (replacing the existing
{% for ticket in TRANSACTION.TICKETS %}block):{% set ns_tickets = namespace(seen=[]) %}
{% for ticket in TRANSACTION.TICKETS %}
{% set ticket_key = ticket.PERFORMANCE_START_DATE.strftime('%Y-%m-%d') + ticket.PERFORMANCE_START_TIME.strftime('%H:%M') + '|' + ticket.BAND_NAME %} {% if ticket_key not in ns_tickets.seen %}
{% set ns_tickets.seen = ns_tickets.seen + [ticket_key] %} {% set count = TRANSACTION.TICKETS | selectattr('PERFORMANCE_START_DATE', 'equalto', ticket.PERFORMANCE_START_DATE) | selectattr('PERFORMANCE_START_TIME', 'equalto', ticket.PERFORMANCE_START_TIME) | selectattr('BAND_NAME', 'equalto', ticket.BAND_NAME) | list | length %}Add a Qty
<td>cell inside the ticket row, between the Item and Price cells:<td
width="10%"
align="right"
style="
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 24px;
padding: 15px 10px 5px 10px;
text-align: center;
vertical-align: text-top;
" >
{{ count }}
</td>Close the deduplication block by adding
{% endif %}before{% endfor %}in the ticket loop.Repeat steps 3–5 for the Products loop
Same pattern, keying onproduct.PRODUCT_TITLE + '|' + product.VARIANT_NAME.
How can I change the start time to 24hr format?
How can I change the start time to 24hr format?
In the line
ticket.PERFORMANCE_START_TIME.strftime('%I:%M %p')Updating
('%I:%M %p')to('%H:%M')will change the format to 24h.
If you want to retain the AM/PM format, update the
('%I:%M %p')to('%-I:%M %p')and that will remove the leading zero.
Is there a Merge Tag for Performance Day of the Week?
Is there a Merge Tag for Performance Day of the Week?
We don't have a specific Merge Tag for Day of the Week, however we do have {{ ticket.PERFORMANCE_START_DATE }} which you can use for if statements.
If you wanted to only show text only if a Transaction contains tickets for a certain Performance Day, e.g. Tuesday, then the code you would want is:
{% if ticket.PERFORMANCE_START_DATE.strftime('%A') == 'Tuesday' %}
TEXT
{% endif %}
Is there a way to group tickets together in a template?
Is there a way to group tickets together in a template?
Yes, you can use a groupby filter as part of your code. groupby allows you to group tickets that have the same characteristics together in sections.
You can choose which field to group by. Use the field name in the groupby, not the display Merge Tag:
Field (use in groupby) | Groups by |
EVENT.ID | Event |
PERFORMANCE_START_DATE | Performance Start Date |
PERFORMANCE_START_TIME | Performance Start Time (use after Start Date to separate a matinee and an evening on the same day) |
SEAT_GROUP_AREA | Area, e.g. Stalls |
SEAT_GROUP_SECTION | Section within an Area |
BAND_NAME | Band Name |
VARIANT_NAME | Variant Name |
SEAT_TYPE | Seat Type (seats with no Seat Type group under an empty name) |
SEAT_GROUP_ROW | Row |
GROUP_BY_ID | Contiguous seat block within a Row |
TRANSACTION_TYPE | Sales vs returned items |
PRODUCT_TITLE | Product (in the Products loop) |
NOTE - groupby only accepts a collection that comes straight from TRANSACTION.TICKETS or TRANSACTION.PRODUCTS, or from another groupby of one of them. A list you build yourself in the Template (with selectattr, list, or by joining lists together) cannot then be grouped, and the Template will not save. If two sets of tickets need to display differently, add the field that separates them (for example TRANSACTION_TYPE) to the groupby chain rather than splitting the list first.
For example, if grouped by Event, the line of code would be:
{% for event_id, event_tickets in TRANSACTION.TICKETS | groupby('EVENT.ID') %}
A common setup in templates is grouping by event, then performance, then band and ticket type. This displays one line per price band with the rows listed beneath.
So the code would look like this:
{% for event_id, event_tickets in TRANSACTION.TICKETS | groupby('EVENT.ID') %}
{% for perf_date, perf_tickets in event_tickets | groupby('PERFORMANCE_START_DATE') %}
{% for perf_time, time_tickets in perf_tickets | groupby('PERFORMANCE_START_TIME') %}
{% set header = time_tickets | first %}
<!-- Event, venue and performance time go here, printed once -->
{% for band_name, band_tickets in time_tickets | groupby('BAND_NAME') %}
{% for variant_name, variant_tickets in band_tickets | groupby('VARIANT_NAME') %}
{% set band_ticket = variant_tickets | first %}
<!-- One line per band and ticket type -->
{% if band_ticket.SEAT_GROUP_AREA != '' %}{{ band_ticket.SEAT_GROUP_AREA }} - {% endif %}{{ band_ticket.BAND_NAME }} - {{ band_ticket.VARIANT_NAME }}
{% for row_name, row_tickets in variant_tickets | groupby('SEAT_GROUP_ROW') %}
{% if row_name != '' %}Row {{ row_name }}, Seats ...{% else %}Quantity: {{ row_tickets | length }}{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endfor %}
NOTE - the != '' checks matter for General Admission tickets: their Area, Row and Seat Type are empty strings (not null), and their Seat Label is the literal "GA", so without the checks a GA Transaction prints "Row , Seats GA-GA". Test for != '' rather than is not none. And always print the Band alongside the Variant, otherwise two lines at different prices look like duplicates.
And it would appear in your email like this:
If you have Reserved Seating and want seats to appear in one seat range rather than individually (as above), then make sure you add this line:
{% for row_name, row_tickets in variant_tickets | groupby('SEAT_GROUP_ROW') %}
Row {{ row_name }}, Seat{% if row_tickets | length > 1 %}s{% endif %} {% for group_id, tickets in row_tickets | groupby('GROUP_BY_ID') %}{% set first_seat = tickets | first %}{% set last_seat = tickets | last %}{{ first_seat.SEAT_LABEL }}{% if tickets | length > 1 %}–{{ last_seat.SEAT_LABEL }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}<br />
This also ensures that each row of seats are grouped as separate lines but underneath their Area and Band.
NOTE - the range prints the first and last ticket in each seat block, which assumes the seats come out in ascending order; a block has been seen to produce "Seats 6-1". If an Area has several Sections, add groupby('SEAT_GROUP_SECTION') to the chain, otherwise two blocks in different Sections collapse into one line (for example "Row B, Seats 1-4, 1-5").
Exchanges - on an Exchange, the Transaction holds both the returned and the new tickets. Without a split they land in the same Band and Variant group and net against each other (a real example rendered as one line, "Standard - Adult Quantity: 7 £-45.99", for 4 returned and 3 new tickets). Add TRANSACTION_TYPE to the chain and label anything that is not a sale:
{% for txn_type, type_tickets in time_tickets | groupby('TRANSACTION_TYPE') %}
...
{% if txn_type != 'SALE' %}<b>Returned:</b> {% endif %}{{ band_ticket.BAND_NAME }} - {{ band_ticket.VARIANT_NAME }}
...
{% endfor %}
This keeps quantities and values separate, and the item lines still add up to {{ TRANSACTION.GROSS }}.
NOTES - a few things grouping cannot fix on its own:
Grouping on PERFORMANCE_START_DATE alone puts a matinee and an evening performance on the same day under one heading. Add groupby('PERFORMANCE_START_TIME') after it, as in the example above.
Discounted and full-price tickets in the same Band and Variant share one line: the total is right, but the line does not show that the prices differ.
A unit-price line such as {{ variant_tickets | length }} x {{ band_ticket.GROSS }} uses the first ticket's price, so it can contradict the line total; avoid it unless every ticket in the group is the same price.
If you'd like to use a full example template with ticket grouping, our Example Templates all include ticket grouping.
Are there Merge Tags I can use to show info for an entire Order instead of an individual Transaction?
Are there Merge Tags I can use to show info for an entire Order instead of an individual Transaction?
Yes, we have Order Merge Tags you can use for this.
Learn more - Full List of Merge Tags (Order Tags section)

