A Mautic 6 plugin that enables full Twig templating in emails, allowing conditionals, loops, and filters with API token data.
Mautic's default token system ({contactfield=firstname}) is limited to simple value replacement. If you need conditional logic, loops, or dynamic content based on API data, you're stuck.
This plugin solves that by:
- Processing Twig syntax (
{{ }},{% %}) directly in your emails - Working with tokens passed via the API
- Automatically handling GrapeJS editor quirks
- Failing gracefully - emails still send even if Twig has errors
- Conditionals - Show/hide content based on values
- Loops - Iterate over arrays (order items, product lists, etc.)
- Filters - Format dates, numbers, text transformations
- Automatic processing - No special markers required
- GrapeJS compatible - Auto-fixes HTML entity encoding (
>to>) - Safe - Errors are logged, but emails still deliver
- Processes everything - Subject line, HTML body, and plain text
- Mautic 6.0+
- PHP 8.1+
-
Download or clone this repository
-
Rename the folder to
MauticTwigEnhancementsBundle -
Copy to your Mautic
plugins/directory:plugins/MauticTwigEnhancementsBundle/ -
Clear Mautic cache:
php bin/console cache:clear
-
Go to Settings > Plugins and click Install/Upgrade Plugins
-
The plugin should appear as "Twig Enhancements"
After installation, the plugin processes emails automatically. No configuration needed.
You can use both Mautic tokens and Twig syntax:
| Format | Type | Example |
|---|---|---|
{token_name} |
Mautic token | {order_id} |
{{ token_name }} |
Twig variable | {{ order_id }} |
{{ tokens.token_name }} |
Via tokens object | {{ tokens.order_id }} |
{{ lead.fieldname }} |
Contact field | {{ lead.firstname }} |
{% if orderTotal > 100 %}
You qualify for free shipping!
{% else %}
Add ${{ 100 - orderTotal }} more for free shipping.
{% endif %}For optional fields, always check if defined:
{% if discount is defined and discount %}
You saved {{ discount }}!
{% endif %}Or use the default filter:
Hello {{ lead.firstname|default('there') }}!Perfect for order confirmations, product lists, etc.:
{% for item in items %}
- {{ item.name }}: ${{ item.price }}
{% endfor %}With index:
{% for item in items %}
{{ loop.index }}. {{ item.name }}
{% endfor %}All standard Twig filters work:
{{ lead.firstname|upper }} {# JOHN #}
{{ lead.firstname|lower }} {# john #}
{{ lead.firstname|capitalize }} {# John #}
{{ lead.firstname|title }} {# John Doe #}
{{ orderDate|date('F j, Y') }} {# January 15, 2024 #}
{{ orderDate|date('m/d/Y') }} {# 01/15/2024 #}
{{ price|number_format(2, '.', ',') }} {# 1,234.56 #}
{{ description|slice(0, 100) }}... {# First 100 chars #}
{{ items|length }} {# Count of items #}
{{ items|first }} {# First item #}
{{ items|last }} {# Last item #}Access any contact field:
Hello {{ lead.firstname }}!
{% if lead.country == 'Jamaica' %}
Free shipping to Jamaica!
{% endif %}
Your email: {{ lead.email }}
Your points: {{ lead.points|default(0) }}contact is an alias for lead:
{{ contact.firstname }} {# Same as lead.firstname #}When sending emails via Mautic API, pass tokens in your payload:
{
"email": 1,
"contact": 123,
"tokens": {
"order_id": "ORD-12345",
"date_time": "2024-01-15 10:30 AM",
"phone_number": "+1 876 555 1234",
"operator": "Digicel",
"country": "Jamaica",
"received_amount": "J$500.00",
"cost": "$5.00",
"processing_fee": "$0.50",
"total": "$5.50",
"discount": "$1.00",
"credit_used": "$2.00",
"receipt_text": "Thank you for your purchase!",
"items": [
{"name": "Airtime", "price": 5.00},
{"name": "Data Bundle", "price": 10.00}
]
}
}Note: Token keys work with or without curly braces (order_id or {order_id}).
When using the GrapeJS email builder (Mautic's default), you need to wrap Twig control structures in <mj-raw> tags to preserve them during MJML compilation.
| Syntax | Needs <mj-raw>? |
Example |
|---|---|---|
{% if %} |
Yes | <mj-raw>{% if x %}</mj-raw> |
{% endif %} |
Yes | <mj-raw>{% endif %}</mj-raw> |
{% for %} |
Yes | <mj-raw>{% for item in items %}</mj-raw> |
{% endfor %} |
Yes | <mj-raw>{% endfor %}</mj-raw> |
{{ variable }} |
No | Works inside <mj-text> directly |
<!-- Conditional section - wrap control tags in mj-raw -->
<mj-raw>{% if discount is defined and discount %}</mj-raw>
<mj-section>
<mj-column>
<mj-text>You saved {{ discount }}!</mj-text>
</mj-column>
</mj-section>
<mj-raw>{% endif %}</mj-raw>GrapeJS converts < and > to < and >. The plugin automatically converts these back inside Twig tags, so comparisons work:
<!-- This works - plugin auto-fixes the > -->
<mj-raw>{% if count > 10 %}</mj-raw>You don't need:
{% TWIG_BLOCK %}markers (unlike Advanced Templates Bundle)- HTML comment workarounds (
<!-- {% if x > 1 %} -->)
If you're coming from mautic-advanced-templates-bundle:
| Advanced Templates | This Plugin |
|---|---|
{% TWIG_BLOCK %}...{% END_TWIG_BLOCK %} |
Not needed - just write Twig |
<mj-raw>{% if ... %}</mj-raw> |
{% if ... %} |
Comment workarounds for > < |
Auto-fixed |
| Requires markers for processing | Processes all Twig automatically |
- Remove all
{% TWIG_BLOCK %}and{% END_TWIG_BLOCK %}markers - Remove
<mj-raw>wrappers around Twig code - Remove HTML comment workarounds (
<!-- {% if x > 1 %} -->) - Keep your actual Twig logic as-is
- Syntax errors: Original content is preserved, email still sends
- Missing variables: Use
|default()filter oris definedcheck - All errors logged: Check
var/logs/mautic.logfor debugging
Example log entry:
[ERROR] TwigEnhancements: Template processing failed {"error":"...","file":"...","line":...}
- Ensure folder is named exactly
MauticTwigEnhancementsBundle - Clear cache:
php bin/console cache:clear - Check file permissions (readable by web server)
- If cloned as root: The plugin directory may be owned by root, preventing Mautic from loading it. Fix with:
(Replace
chown -R www-data:www-data plugins/MauticTwigEnhancementsBundle
www-datawith your web server user if different)
If the plugin doesn't appear in the UI but you want to confirm it's loaded:
# Check if services are registered
php bin/console debug:container | grep -i twig
# Check event listeners
php bin/console debug:event-dispatcher EmailEvents::EMAIL_ON_SEND
# Check if bundle is loaded
php bin/console debug:container --parameter=kernel.bundles | grep Twig- Verify plugin is installed: Settings > Plugins
- Check Mautic logs:
var/logs/mautic.log - Validate Twig syntax at twigfiddle.com
- Token names are case-sensitive
- API tokens need curly braces:
"{order_id}": "value" - Use
{{ variable|default('fallback') }}for optional values - Check with:
{% if variable is defined %}...{% endif %}
- This shouldn't happen, but if it does, try the Code view in GrapeJS
- Ensure you're not inside an MJML component that escapes content
- Plugin listens to
EMAIL_ON_SENDandEMAIL_ON_DISPLAYevents - Checks if content contains Twig syntax (
{{,{%,{#) - Fixes HTML entities inside Twig tags (GrapeJS compatibility)
- Merges API tokens + lead data into Twig context
- Renders template through Twig engine
- On error: logs issue, returns original content
MIT License - see LICENSE file.
Contributions welcome! Please feel free to submit a Pull Request.
Inspired by: