Module 21: Troubleshooting
How Errors Work in Cast’s Strict Mode
Section titled “How Errors Work in Cast’s Strict Mode”Cast runs Liquid in strict mode, which means errors are surfaced immediately rather than silently producing blank or wrong output. While this is stricter than standard Shopify Liquid, it’s ultimately a feature, not a bug — it catches mistakes before they reach customers.
When Cast encounters an error, it typically:
- Stops rendering the affected narration
- Shows an error message indicating what went wrong
- Identifies the approximate location of the problem
The key to troubleshooting is understanding the category of error and knowing the standard fix.
Error Category 1 — Undefined Variable
Section titled “Error Category 1 — Undefined Variable”What it looks like
Section titled “What it looks like”{% raw %}Liquid error: undefined variable 'contct_first_name'{% endraw %}Root cause
Section titled “Root cause”You referenced a variable that doesn’t exist. This is almost always one of three things:
- A typo in the variable name
- A missing field in the CRM data that wasn’t given a default
- A snippet name that doesn’t match exactly (names are case-sensitive)
Typo:
{% raw %}❌ {{ contct_first_name }}{% endraw %}
{% raw %}✅ {{ contact_first_name }}{% endraw %}
Missing CRM field — add a default:
{% raw %}❌ {{ health_score }}{% endraw %}
{% raw %}✅ {{ health_score | default: "N/A" }}{% endraw %}
Snippet name mismatch:
{% raw %}❌ {{ isEMEA }} (wrong case){% endraw %}
{% raw %}✅ {{ IsEMEA }} (matches the snippet name exactly){% endraw %}
💡 Prevention: Use the setup block pattern from Module 15. By assigning all variables with defaults at the top, you catch missing fields before they cause errors deeper in the narration.
Error Category 2 — Unclosed Tag
Section titled “Error Category 2 — Unclosed Tag”What it looks like
Section titled “What it looks like”{% raw %}Liquid error: 'if' tag was never closedLiquid error: missing 'endfor'{% endraw %}Root cause
Section titled “Root cause”Every opening tag needs a matching closing tag. The most common unclosed pairs:
| Opens With | Must Close With |
|---|---|
| {% raw %} | {% if %} |
| {% raw %} | {% unless %} |
| {% raw %} | {% for %} |
| {% raw %} | {% case %} |
| {% raw %} | {% capture x %} |
| {% raw %} | {% comment %} |
Count your opening and closing tags. For complex narrations, add a comment next to each closing tag:
{% raw %}
Top tier enterprise content. {%- # end: tier check -%}{% else %} Standard content. {%- # end: score check -%}{% endraw %}💡 Prevention: Write the closing tag immediately after writing the opening tag, then fill in the content between them.
Error Category 3 — Type Mismatch in Comparisons
Section titled “Error Category 3 — Type Mismatch in Comparisons”What it looks like
Section titled “What it looks like”This one is sneaky — it often doesn’t produce a visible error, but produces wrong results. The narration renders, but the wrong branch is chosen.
Root cause
Section titled “Root cause”Comparing a string to a number, or using > / < on unconverted string values.
Symptoms
Section titled “Symptoms”- A customer with ARR of $150,000 is told “your account is below the threshold” when the threshold is $100,000
- A health score of 85 triggers the “needs improvement” branch
- An
ifblock that should match never triggers
Numeric comparisons — always convert first:
{% raw %}❌ {% if arr > 100000 %}{% endraw %}
{% raw %}✅ {%- assign arr_val = arr | default: 0 | plus: 0 -%}{% if arr_val > 100000 %}{% endraw %}
Snippet boolean comparisons — always compare strings:
{% raw %}❌ {% if IsEMEA == true %}{% endraw %}
{% raw %}✅ {% if IsEMEA == "yes" %} (with yes/no snippet design){% endraw %}
Case-sensitive string comparisons — normalize first:
{% raw %}❌ {% if tier == "enterprise" %} (CRM stores “Enterprise”){% endraw %}
{% raw %}✅ {%- assign tier = product_tier | downcase -%}{% if tier == "enterprise" %}{% endraw %}
💡 Prevention: Every numeric field gets | plus: 0 in the setup block. Every snippet result gets | strip. Every string comparison uses normalized casing.
Error Category 4 — Division by Zero
Section titled “Error Category 4 — Division by Zero”What it looks like
Section titled “What it looks like”{% raw %}Liquid error: divided by 0{% endraw %}Root cause
Section titled “Root cause”Dividing by a variable that is zero or was never set. Common in percentage calculations where the denominator comes from CRM data.
Always guard division with a zero check:
{% raw %}{%- assign reserved = LicenseReserved | default: 0 | times: 1.0 -%}
{%- assign pct = used | divided_by: reserved | times: 100 | round: 0 -%} Your utilization is {{ pct }}%.{% else %} No reserved capacity on file.
{% endraw %}💡 Prevention: Before any divided_by, check that the denominator is greater than zero.
Error Category 5 — Filter on Nil Value
Section titled “Error Category 5 — Filter on Nil Value”What it looks like
Section titled “What it looks like”{% raw %}Liquid error: nil is not a stringLiquid error: undefined method 'size' for nil{% endraw %}Root cause
Section titled “Root cause”Applying a filter (like size, split, upcase, cast_titlecase) to a variable that is nil.
Add default before the filter that’s failing:
{% raw %}❌ {{ contact_account_name | cast_titlecase }}{% endraw %}
{% raw %}✅ {{ contact_account_name | default: "your company" | cast_titlecase }}{% endraw %}
{% raw %}❌ {%- assign items = feature_list | split: "," -%}{% endraw %}
{% raw %}✅ {%- assign items = feature_list | default: "" | split: "," -%}{% endraw %}
💡 Prevention: In your setup block, every variable that might be nil gets a default before any other filter is applied.
Error Category 6 — Whitespace Causing Silent Failures
Section titled “Error Category 6 — Whitespace Causing Silent Failures”What it looks like
Section titled “What it looks like”No visible error — but conditions that should match don’t, and content that should appear is missing.
Root cause
Section titled “Root cause”Snippet output or CRM fields contain invisible leading/trailing whitespace. "yes" and " yes " are different strings.
Diagnosis
Section titled “Diagnosis”Temporarily display the raw value with brackets to make whitespace visible:
{% raw %}DEBUG: [{{ IsEMEA }}] length={{ IsEMEA | size }}{% endraw %}If you see [ yes ] with spaces, or a length longer than expected, whitespace is the culprit.
Always strip before comparing:
{% raw %}{%- assign region = IsEMEA | strip -%}
{% endraw %}💡 Prevention: Make | strip automatic when assigning any snippet result.
Error Category 7 — Integer vs. Decimal Division
Section titled “Error Category 7 — Integer vs. Decimal Division”What it looks like
Section titled “What it looks like”No error — but percentages come out as 0% when they should be 75%, or results are unexpectedly rounded.
Root cause
Section titled “Root cause”Integer division drops the decimal part. 45 / 60 produces 0, not 0.75.
Diagnosis
Section titled “Diagnosis”If a percentage or ratio calculation returns 0 (or a suspiciously round number), check whether you’re using integer or decimal division.
Ensure at least one operand is a decimal:
{% raw %}❌ {%- assign pct = 45 | divided_by: 60 | times: 100 -%} → 0%{% endraw %}
{% raw %}✅ {%- assign pct = 45 | divided_by: 60.0 | times: 100 -%} → 75%{% endraw %}
Or convert with times: 1.0 in the setup block:
{% raw %}{%- assign used = LicenseUsed | default: 0 | times: 1.0 -%}{%- assign reserved = LicenseReserved | default: 0 | times: 1.0 -%}{% endraw %}Error Category 8 — Date Parsing Failures
Section titled “Error Category 8 — Date Parsing Failures”What it looks like
Section titled “What it looks like”{% raw %}Liquid error: invalid date{% endraw %}Or: a date displays as January 01, 1900 or shows raw timestamp numbers.
Root cause
Section titled “Root cause”- The date field is empty or contains non-date text
- The date format from the CRM isn’t recognized by Liquid’s parser
- The sentinel value
-2208988800(blank date) is being formatted
Guard against empty dates:
{% raw %}{%- assign renewal = renewal_date | default: "" -%}
Renews on {{ renewal | date: "%B %d, %Y" }}.
{% endraw %}Check for the sentinel value:
{% raw %}{%- assign ts = some_date | date: "%s" -%}
{%- # Safe to use the date -%}
{% endraw %}Error Category 9 — Unexpected Output from cast_titlecase
Section titled “Error Category 9 — Unexpected Output from cast_titlecase”What it looks like
Section titled “What it looks like”Company name displays with incorrect casing — usually non-English particles like “van,” “de,” or “la” being capitalized when they should be lowercase.
Root cause
Section titled “Root cause”cast_titlecase handles English particles (of, the, and) but capitalizes non-English particles.
Diagnosis
Section titled “Diagnosis”Check the input value. If it contains van, der, de, la, le, or similar non-English particles, this is a known limitation.
Workaround
Section titled “Workaround”For specific known names, use replace after cast_titlecase:
{% raw %}{{ contact_account_name | cast_titlecase | replace: "Van Der", "van der" }}{% endraw %}This is a manual fix for specific cases — not a general solution.
Debugging Checklist
Section titled “Debugging Checklist”When something isn’t working, run through this checklist:
- Is the variable name spelled correctly? Check for typos, case sensitivity.
- Does the variable have a value? Add
defaultif it might be nil. - Is the data type correct? Convert strings to numbers before math/comparisons.
- Are all tags closed? Count
if/endif,for/endfor, etc. - Is there hidden whitespace? Use
| stripon snippet results. - Is division safe? Check for zero denominators.
- Are strings being compared to strings? Quote the comparison values.
- Is the date valid? Check for blank, nil, and sentinel values.
- Is the output what you expect? Use temporary debug output to inspect values.
Debug output pattern
Section titled “Debug output pattern”{% raw %}{%- # TEMPORARY — remove before publishing -%}DEBUG score: [{{ health_score }}] type-check: [{{ health_score | plus: 0 }}]DEBUG region: [{{ IsEMEA }}] stripped: [{{ IsEMEA | strip }}]DEBUG arr: [{{ arr }}] numeric: [{{ arr | plus: 0 }}]{% endraw %}The square brackets make whitespace visible. The | plus: 0 test confirms whether a value converts to a number successfully.
Quick Fix Reference Table
Section titled “Quick Fix Reference Table”| Symptom | Likely Cause | Fix |
|---|---|---|
| “undefined variable” error | Typo or missing default | Check spelling; add | default: |
| “‘if’ tag was never closed” | Missing endif |
Add matching closing tag |
Wrong branch chosen in if |
String vs. number comparison | Convert with | plus: 0 before comparing |
| Percentage shows 0% | Integer division | Use | times: 1.0 on operands |
| {% raw %} | “divided by 0” error | Zero denominator |
| Snippet condition never matches | Whitespace in output | Add | strip before comparison |
| “nil is not a string” | Filter on nil value | Add | default: before other filters |
| Date shows 1900 or garbage | Invalid/empty date field | Guard with blank check before formatting |
| Company name cased wrong | Non-English particles | Known limitation; use replace workaround |
| {% raw %} | {% if IsEMEA %} always true |
Testing string as boolean |
Try It Yourself
Section titled “Try It Yourself”Exercise: The following narration has five bugs. Find and fix all of them.
{% raw %}{% assign name = contact_first_name %}{% assign score = health_score %}{% assign company = contact_account_name | cast_titlecase %}
Hi {{ name }},
Welcome to {{ company | cast_apostrophe }} review.
Great score!{% elsif score > 60 } Good score.
Your renewal is on {{ renewal_date | date: "%B %d, %Y" }}.
EMEA content.
{% endraw %}Click to reveal the answer
Bug 1: No default on contact_first_name — errors if nil.
{% raw %}✅ {% assign name = contact_first_name | default: "there" %}{% endraw %}
Bug 2: health_score not converted to number — comparison unreliable.
{% raw %}✅ {% assign score = health_score | default: 0 | plus: 0 %}{% endraw %}
Bug 3: No default before cast_titlecase — errors if nil.
{% raw %}✅ {% assign company = contact_account_name | default: "your company" | cast_titlecase %}{% endraw %}
Bug 4: Missing % in closing elsif tag and missing endif.
{% raw %}❌ {% elsif score > 60 } → ✅ {% elsif score > 60 %}{% endraw %}
{% raw %}Also missing {% else %} and {% endif %} for the score block.{% endraw %}
Bug 5: Comparing snippet to boolean true instead of string.
{% raw %}✅ {% if IsEMEA == "yes" %} (assuming yes/no snippet design){% endraw %}
Also: renewal_date has no guard for nil — should check before formatting.
Fixed version:
{% raw %}{%- assign name = contact_first_name | default: "there" -%}{%- assign score = health_score | default: 0 | plus: 0 -%}{%- assign company = contact_account_name | default: "your company" | cast_titlecase -%}{%- assign renewal = renewal_date | default: "" -%}{%- assign region = IsEMEA | default: "" | strip -%}
Hi {{ name }},
Welcome to {{ company | cast_apostrophe }} review.
Great score!{% elsif score >= 60 %} Good score.{% else %} Let's work on improving things.
Your renewal is on {{ renewal | date: "%B %d, %Y" }}.
EMEA content.
{% endraw %}What’s Next
Section titled “What’s Next”In Module 22, you’ll get the Quick Reference Card — a compact, single-page reference with every filter, tag, and pattern you need for daily Cast Liquid work.
📖 Official documentation:
- Overview: https://school.cast.app/liquid.html
- Tags/Blocks: https://school.cast.app/liquid/liquid-blocks.html
- Standard Filters: https://school.cast.app/liquid/liquid-filters.html
- Custom Cast Filters: https://school.cast.app/liquid/custom-cast-filters.html