Module 15: Variables — assign and capture
Recap — assign vs. capture
Section titled “Recap — assign vs. capture”Before diving into advanced patterns, a quick reminder:
| Tool | What It Stores | Best For |
|---|---|---|
| {% raw %} | {% assign x = value %} |
A single value (with optional filter chain) |
| {% raw %} | {% capture x %}...{% endcapture %} |
Everything between the tags as a string |
{% raw %}Both create variables you can use later with {{ x }}.{% endraw %}
Pattern 1 — The Setup Block
Section titled “Pattern 1 — The Setup Block”The most important advanced pattern is the setup block at the top of a narration. This is where you do all your data preparation — defaults, conversions, and normalization — so the narration body is clean and readable.
{% raw %}{% comment %} QBR Welcome Slide Sources: Salesforce (contact, ARR), Gainsight (health score) Snippets: CustomerSince, IsEMEA Updated: 2026-03-20{% endcomment %}
{%- # === Text fields === -%}{%- assign name = contact_first_name | default: "there" -%}{%- assign company = contact_account_name | default: "your company" | cast_titlecase -%}{%- assign csm = csm_name | default: "your account manager" -%}{%- assign tier = product_tier | default: "" | strip | downcase -%}
{%- # === Numeric fields === -%}{%- assign score = health_score | default: 0 | plus: 0 -%}{%- assign arr_val = arr | default: 0 | plus: 0 -%}
{%- # === Date fields === -%}{%- assign renewal = renewal_date | default: "" -%}
{%- # === Snippet results === -%}{%- assign region = IsEMEA | strip -%}{%- assign tenure = CustomerSince -%}{% endraw %}After this block, the narration body uses only these clean, typed, safe variables. This separation has three major benefits:
- Errors are isolated. If a field is missing, the default catches it in one place.
- Readability. The narration body reads like plain English with variable insertions.
- Maintainability. When a CRM field name changes, you update one line in the setup block.
Pattern 2 — Conditional Assignment
Section titled “Pattern 2 — Conditional Assignment”Sometimes you need a variable’s value to depend on a condition. There are two approaches:
Approach A — Assign a default, then overwrite conditionally:
{% raw %}{%- assign message = "We've prepared some recommendations for you." -%}
{%- if score >= 80 -%} {%- assign message = "Your account is performing excellently!" -%}{%- elsif score >= 60 -%} {%- assign message = "Your account shows solid engagement with room to grow." -%}{%- endif -%}
{{ message }}{% endraw %}This sets a default message first, then replaces it if a higher-tier condition is met. The advantage: the variable is always set, so you can safely use it anywhere below.
Approach B — Use capture with conditions inside:
{% raw %}{%- capture message -%} {%- if score >= 80 -%} Your account is performing excellently! {%- elsif score >= 60 -%} Your account shows solid engagement with room to grow. {%- else -%} We've prepared some recommendations for you. {%- endif -%}{%- endcapture -%}
{{ message | strip }}{% endraw %}This builds the message inside a capture block using conditional logic. Use | strip when outputting because the whitespace from the conditional branches can add extra spaces.
💡 Which to choose? If the message is a short, single sentence, Approach A is simpler. If the message is long or includes variables and filters, Approach B keeps everything together.
Pattern 3 — Multi-Part Sentence Assembly
Section titled “Pattern 3 — Multi-Part Sentence Assembly”When a sentence has multiple optional parts based on data, capture shines:
{% raw %}{%- assign score = health_score | default: 0 | plus: 0 -%}{%- assign arr_val = arr | default: 0 | plus: 0 -%}{%- assign company = contact_account_name | default: "Your company" | cast_titlecase -%}
{%- capture summary -%} {{ company }} has {%- if score >= 80 %} a strong health score of {{ score }} {%- elsif score > 0 %} a health score of {{ score }} {%- else %} no health score on record {%- endif -%} {%- if arr_val > 0 %} and an ARR of ${{ arr_val }}{%- endif -%} .{%- endcapture -%}
{{ summary | strip }}{% endraw %}For Acme LLC with score 85 and ARR 200000:
Acme LLC has a strong health score of 85 and an ARR of $200000.
For Globex Inc with score 0 and no ARR:
Globex Inc has no health score on record.
The sentence structure adapts dynamically. The period at the end always appears, regardless of which branches are taken.
Pattern 4 — Pluralization Helper
Section titled “Pattern 4 — Pluralization Helper”A common need in narrations: choosing between singular and plural forms based on a count.
{% raw %}{%- assign task_count = tasks | size -%}
{%- capture task_word -%} {%- if task_count == 1 -%}task{%- else -%}tasks{%- endif -%}{%- endcapture -%}
You have {{ task_count }} open {{ task_word }}.{% endraw %}For 1 task: You have 1 open task.
For 5 tasks: You have 5 open tasks.
You can also inline this without capture using the snippet pattern from the prompt:
{% raw %}{{ ActiveLearners }} active learner{%- if ActiveLearners != "1" %}scompleted {{ Courses }} course{%- if Courses != "1" %}s this month.{% endraw %}⚠️ Note the comparison is against the string "1", not the number 1 — because snippet output and many CRM fields are strings.
Pattern 5 — Accumulating Inside a Loop
Section titled “Pattern 5 — Accumulating Inside a Loop”Sometimes you need to process each item in a loop and build a result. Since Liquid doesn’t have a “push” or “append to array” operation that works intuitively, the common workaround is to accumulate into a string and optionally split at the end:
{% raw %}{%- assign features = feature_list | split: "," -%}{%- assign premium_list = "" -%}
{%- for feature in features -%} {%- assign clean = feature | strip -%} {%- if clean == "Analytics" or clean == "Advanced Reporting" or clean == "API Access" -%} {%- if premium_list != "" -%} {%- assign premium_list = premium_list | append: "," -%} {%- endif -%} {%- assign premium_list = premium_list | append: clean -%} {%- endif -%}{%- endfor -%}
{%- assign premium_features = premium_list | split: "," -%}
You're using {{ premium_features.size }} premium features: {{ premium_features | join: ", " }}.{% else %} You're not currently using any premium features.
{% endraw %}This loops through all features, filters for “premium” ones, accumulates them as a comma-separated string, then splits back into an array for counting and display.
Pattern 6 — Flag Variables
Section titled “Pattern 6 — Flag Variables”Use a simple variable as a flag to track whether something happened during processing:
{% raw %}{%- assign has_urgent_task = "no" -%}
{%- assign tasks = open_tasks | split: "," -%}{%- for task in tasks -%} {%- assign clean = task | strip | downcase -%} {%- if clean contains "renew" or clean contains "urgent" -%} {%- assign has_urgent_task = "yes" -%} {%- endif -%}{%- endfor -%}
⚠️ You have at least one urgent task that needs attention.
{% endraw %}The flag starts as "no" and flips to "yes" if any task matches the criteria. After the loop, the flag controls whether the warning message appears.
Variable Scope — What You Need to Know
Section titled “Variable Scope — What You Need to Know”In Liquid, variables created with assign or capture are available everywhere after the point of assignment in the same template. There’s no “block scope” — a variable created inside an if block or for loop is still accessible after the block ends:
{% raw %}{%- assign x = "before" -%}
{%- assign x = "inside if" -%} {%- assign y = "also inside if" -%}
{{ x }} → "inside if" (changed by the inner assign){{ y }} → "also inside if" (still accessible outside the block){% endraw %}This is useful (you can set variables inside conditions and use them later) but also a potential source of confusion. Be intentional about where you assign variables, and use the setup block pattern to keep things organized.
Common Mistakes
Section titled “Common Mistakes”❌ Forgetting that capture stores a string, not a number:
{% raw %}{% capture result %}{{ price | plus: tax }}{% endcapture %}
{% endraw %}result is a string after capture. Compare with == or convert first.
✅ Convert after capturing:
{% raw %}{% capture result %}{{ price | plus: tax }}{% endcapture %}{%- assign result_num = result | strip | plus: 0 -%}
{% endraw %}❌ Assigning inside a conditional without a fallback:
{% raw %}
{%- assign tier_label = "Top Tier" -%}{% elsif score >= 60 %} {%- assign tier_label = "Mid Tier" -%}
Your {{ tier_label }} account...{% endraw %}If score is below 60, tier_label is never assigned — causing an error in strict mode.
✅ Always set a default before the conditional:
{% raw %}{%- assign tier_label = "Standard" -%}
{%- assign tier_label = "Top Tier" -%}{% elsif score >= 60 %} {%- assign tier_label = "Mid Tier" -%}
Your {{ tier_label }} account...{% endraw %}❌ Over-using capture for simple values:
{% raw %}{% capture name %}{{ contact_first_name | default: "there" }}{% endcapture %}{% endraw %}✅ Use assign for simple values:
{% raw %}{%- assign name = contact_first_name | default: "there" -%}{% endraw %}Try It Yourself
Section titled “Try It Yourself”Exercise: You have three fields: score (converted to number, value 72), arr_val (converted to number, value 150000), and tier (string, value “Professional”). Using capture, build a single summary sentence that adapts based on the data:
- Start with the tier name
- Add a health score descriptor (“excellent” for 80+, “solid” for 60+, “needs attention” otherwise)
- Add ARR if greater than 100000
- End with a period
Target output: Your Professional account has solid health and an ARR of $150000.
Click to reveal the answer
{% raw %}{%- assign score = 72 -%}{%- assign arr_val = 150000 -%}{%- assign tier = "Professional" -%}
{%- capture summary -%} Your {{ tier }} account has {%- if score >= 80 %} excellent health {%- elsif score >= 60 %} solid health {%- else %} health that needs attention {%- endif -%} {%- if arr_val > 100000 %} and an ARR of ${{ arr_val }}{%- endif -%} .{%- endcapture -%}
{{ summary | strip }}{% endraw %}Output: Your Professional account has solid health and an ARR of $150000.
If the score were 45 and ARR were 50000:
Your Professional account has health that needs attention.
What’s Next
Section titled “What’s Next”In Module 16, you’ll learn how snippets work in Cast — how they’re created, how they return values, and the critical patterns for using snippet output correctly in your narrations.
📖 Official documentation:
- Tags/Blocks: https://school.cast.app/liquid/liquid-blocks.html