Liquid Templates
AppFunnel uses Liquid as its template language for rendering dynamic content. Liquid templates work in text elements, HTML blocks, custom CSS, and event payloads — anywhere you need to display or compute values from variables.
Basic Interpolation
Wrap a variable name in double curly braces to output its value:
Hello, {{ user.name }}!If user.name is "Sarah", this renders as: Hello, Sarah!
You can reference any variable namespace:
Your goal: {{ answers.incomeGoal }}
Device: {{ device.type }}
Browser: {{ browser.name }}
Campaign: {{ query.utm_campaign }}Nested Access
Product variables use dot notation to access properties:
{{ products.monthly.price }}
{{ products.selected.period }}
{{ products.yearly.trialDays }}Filters
Filters transform the output of a variable. They are applied with a pipe (|) character after the variable name. Filters can be chained — each filter receives the output of the previous one.
{{ variable | filter }}
{{ variable | filter1 | filter2 | filter3 }}Math Filters
Use math filters for arithmetic operations on numbers.
| Filter | Description | Example | Result |
|---|---|---|---|
divided_by | Division | {{ 100 | divided_by: 3 }} | 33 |
times | Multiplication | {{ 5 | times: 3 }} | 15 |
plus | Addition | {{ 10 | plus: 5 }} | 15 |
minus | Subtraction | {{ 10 | minus: 3 }} | 7 |
modulo | Remainder | {{ 10 | modulo: 3 }} | 1 |
Practical: Calculate monthly price from yearly
{{ products.yearly.rawPrice | divided_by: 12 | round: 2 }}If the yearly plan is $59.99, this outputs 5.0 (the raw numeric result of 59.99 / 12, rounded to 2 decimal places).
Practical: Calculate savings percentage
{% assign monthlyTotal = products.monthly.rawPrice | times: 12 %}
{% assign yearlySavings = monthlyTotal | minus: products.yearly.rawPrice %}
{% assign savingsPercent = yearlySavings | divided_by: monthlyTotal | times: 100 | round: 0 %}
Save {{ savingsPercent }}% with the yearly planNumber Formatting Filters
| Filter | Description | Example | Result |
|---|---|---|---|
round | Round to nearest integer (or N decimal places) | {{ 3.7 | round }} | 4 |
round: 2 | Round to N decimal places | {{ 3.14159 | round: 2 }} | 3.14 |
floor | Round down | {{ 3.9 | floor }} | 3 |
ceil | Round up | {{ 3.1 | ceil }} | 4 |
abs | Absolute value | {{ -5 | abs }} | 5 |
to_fixed: N | Format to exactly N decimal places | {{ 9.9 | to_fixed: 2 }} | 9.90 |
round and to_fixed behave differently. round: 2 returns a number (3.14), while to_fixed: 2 returns a string with trailing zeros (3.14 or 9.90). Use to_fixed when you want consistent decimal formatting for prices.
Practical: Format a per-day price
Only {{ products.yearly.rawPrice | divided_by: 365 | to_fixed: 2 }} per dayResult: Only 0.16 per day (for a $59.99/year plan).
String Filters
| Filter | Description | Example | Result |
|---|---|---|---|
upcase | Uppercase | {{ "hello" | upcase }} | HELLO |
downcase | Lowercase | {{ "Hello" | downcase }} | hello |
capitalize | Capitalize first letter | {{ "hello world" | capitalize }} | Hello world |
size | String or array length | {{ "hello" | size }} | 5 |
The default Filter
Provide a fallback value when a variable is empty, nil, or false:
Hello, {{ answers.name | default: "friend" }}!If answers.name hasn’t been set yet, this renders: Hello, friend!
This is especially useful for pages that can be reached before a variable is populated:
Your goal: {{ answers.incomeGoal | default: "not set yet" }}
Selected plan: {{ products.selected.displayName | default: "none" }}Conditionals
Liquid supports if, elsif, else, and endif for conditional rendering. Content inside the tags is only rendered when the condition is true.
Basic If/Else
{% if answers.experience == 'advanced' %}
Welcome back, pro!
{% else %}
Let's get you started!
{% endif %}Multiple Branches
{% if answers.goal == 'profit' %}
Let's maximize your earnings.
{% elsif answers.goal == 'savings' %}
Smart saving starts here.
{% elsif answers.goal == 'hobby' %}
Enjoy the thrill of the hunt!
{% else %}
Welcome aboard.
{% endif %}Comparison Operators
Liquid supports these comparison operators inside {% if %} tags:
| Operator | Meaning |
|---|---|
== | Equals |
!= | Not equals |
> | Greater than |
< | Less than |
>= | Greater than or equal |
<= | Less than or equal |
Boolean Logic
Combine conditions with and / or:
{% if answers.experience == 'advanced' and answers.goal == 'profit' %}
You're ready to start flipping for profit.
{% endif %}{% if device.isMobile or device.isTablet %}
Tap to continue
{% else %}
Click to continue
{% endif %}Truthiness
In Liquid, the only falsy values are false and nil. Empty strings, 0, and empty arrays are truthy. To check for an empty string, compare explicitly:
{% if user.email != '' %}
Logged in as {{ user.email }}
{% endif %}Or use the size filter:
{% if answers.interests.size > 0 %}
You selected {{ answers.interests.size }} interests.
{% endif %}Unless
unless is the inverse of if:
{% unless purchase.success %}
Complete your purchase to continue.
{% endunless %}Variables (Assign)
Use assign to create local variables within a template. Useful for intermediate calculations:
{% assign dailyCost = products.yearly.rawPrice | divided_by: 365 %}
{% assign formattedCost = dailyCost | to_fixed: 2 %}
That's just ${{ formattedCost }} per day.Assigned variables only exist within the current template evaluation — they are not stored in the variable system.
Using Liquid in Text Elements
Text elements are the most common place for Liquid templates. The entire text content supports Liquid syntax.
Greeting with fallback
Hey {{ answers.name | default: "there" }}, based on your answers, here's your personalized plan.Product pricing display
{{ products.selected.displayName }}:
{{ products.selected.price }}/{{ products.selected.period }}Result: Monthly Plan: $9.99/month
Trial messaging
{% if products.selected.hasTrial %}
Start your {{ products.selected.trialDays }}-day free trial, then {{ products.selected.price }}/{{ products.selected.period }}.
{% else %}
Get started for {{ products.selected.price }}/{{ products.selected.period }}.
{% endif %}Savings comparison
{% assign monthlyAnnualized = products.monthly.rawPrice | times: 12 %}
{% assign savings = monthlyAnnualized | minus: products.yearly.rawPrice %}
Save ${{ savings | to_fixed: 2 }} per year with the annual plan.Dynamic answer summary
Here's what we know:
- Goal: {{ answers.goal | capitalize }}
- Experience: {{ answers.experience | default: "not specified" }}
- Budget: ${{ answers.budget | default: 0 | to_fixed: 2 }}/monthUsing Liquid in Custom CSS
Liquid templates work in Custom CSS fields on any component. A quick example:
width: {{ page.progressPercentage }}%;
transition: width 0.3s ease;See Custom CSS for the full guide on pseudo-selectors, media queries, keyframes, and more.
Using Liquid in HTML Blocks
HTML Block elements support full Liquid templates within the raw HTML content. This is useful for embedding third-party widgets or creating custom markup:
<div class="welcome-banner">
<h2>Welcome, {{ user.name | default: "friend" }}!</h2>
<p>Your {{ products.selected.periodly }} plan includes:</p>
<ul>
{% if products.selected.hasTrial %}
<li>{{ products.selected.trialDays }}-day free trial</li>
{% endif %}
<li>Full access to all features</li>
<li>Cancel anytime</li>
</ul>
</div>For the complete filter reference, see Liquid Filter Reference.
Real-World Examples
Pricing card with savings badge
{% assign monthlyTotal = products.monthly.rawPrice | times: 12 %}
{% assign savings = monthlyTotal | minus: products.yearly.rawPrice %}
{% assign savingsPercent = savings | divided_by: monthlyTotal | times: 100 | round: 0 %}
{{ products.yearly.price }}/year
That's {{ products.yearly.rawPrice | divided_by: 12 | to_fixed: 2 }}/month — save {{ savingsPercent }}%Countdown display from page time
{% assign remaining = 300 | minus: page.timeOnCurrent %}
{% assign minutes = remaining | divided_by: 60 | floor %}
{% assign seconds = remaining | modulo: 60 | round %}
{{ minutes }}:{% if seconds < 10 %}0{% endif %}{{ seconds }}This counts down from 5 minutes (300 seconds), displaying as 4:59, 4:58, etc.
Personalized summary page
{{ answers.name | default: "Friend" }}, here's your personalized plan:
Based on your {{ answers.experience }} level experience and your goal of {{ answers.goal | downcase }}, we recommend the {{ products.selected.displayName }}.
{% if products.selected.hasTrial %}
Start with a {{ products.selected.trialDays }}-day free trial, then {{ products.selected.price }}/{{ products.selected.period }}.
{% else %}
Get started today for {{ products.selected.price }}/{{ products.selected.period }}.
{% endif %}Dynamic CSS progress bar
In a stack’s Custom CSS field:
width: {{ page.progressPercentage }}%;
height: 4px;
background: linear-gradient(90deg, #6366f1, #8b5cf6);
border-radius: 2px;
transition: width 0.4s ease;Per-day cost in multiple currencies
{% assign dailyCost = products.yearly.rawPrice | divided_by: 365 | to_fixed: 2 %}
Just {{ products.yearly.currencySymbol }}{{ dailyCost }} per dayConditional discount messaging
{% if data.showDiscount %}
Limited time: get {{ data.discountPercent }}% off!
{% assign discounted = products.selected.rawPrice | times: data.discountPercent | divided_by: 100 %}
{% assign finalPrice = products.selected.rawPrice | minus: discounted %}
New price: {{ products.selected.currencySymbol }}{{ finalPrice | to_fixed: 2 }}/{{ products.selected.period }}
{% endif %}Tips and Gotchas
Liquid whitespace can create unexpected gaps in text. Use {%- -%} (with hyphens) to strip whitespace around tags if you see extra spacing in rendered output:
{%- if answers.name -%}Hello, {{ answers.name }}{%- else -%}Hello{%- endif -%}!- Undefined variables render as empty strings — the template engine is lenient and will not throw errors for missing variables.
- Filters can be chained — each filter receives the output of the previous one, evaluated left to right.
- Numeric strings are auto-converted — if a variable contains
"42", math filters will treat it as a number. - Assigned variables are local —
{% assign %}creates template-local variables, not funnel variables. - Product variables use formatted strings —
products.monthly.pricereturns"$9.99"(with symbol). UserawPricewhen you need a number for calculations.