Module 5: Standard Filters — Numbers
Why Number Filters Matter in Cast
Section titled “Why Number Filters Matter in Cast”Almost every Cast narration involves at least one number: a health score, an ARR figure, a license count, a usage percentage, a days-until-renewal calculation. And as you learned in Module 2, these values arrive from your CRM as strings — text that happens to contain digits.
Number filters serve two purposes:
- Converting strings to real numbers so comparisons and math work correctly
- Calculating — adding, subtracting, multiplying, dividing, rounding — to produce the metrics your narration needs
The Critical Lesson — String-to-Number Conversion
Section titled “The Critical Lesson — String-to-Number Conversion”Before we cover any individual filter, let’s revisit why conversion matters with a concrete example.
Suppose arr arrives from Salesforce with the value "95000" (a string). You write:
{% raw %}
High-value account.
{% endraw %}This looks correct but is unreliable. Here’s why: Liquid sees a string on the left ("95000") and a number on the right (50000). When types don’t match, the comparison may fall back to string (alphabetical) rules. Under alphabetical comparison, "9" comes after "5", so "95000" > "50000" happens to be true. But "6" > "50000" would also be true alphabetically — because "6" comes after "5" in character order. That’s clearly wrong numerically.
Excel analogy: This is exactly what happens if you try
=IF(A1>50000, ...)in Excel where cell A1 is formatted as Text. Excel may silently treat it as a string comparison instead of a numeric one, giving you wrong results. The fix in Excel is to change the cell format or wrap it inVALUE(). The fix in Liquid is| plus: 0or| times: 1.
The two conversion methods:
{% raw %}{% assign arr_numeric = arr | plus: 0 %} {%- # adds 0 → forces to number -%}{% assign arr_numeric = arr | times: 1 %} {%- # multiplies by 1 → forces to number -%}{% endraw %}Both produce the same result for integers. The difference matters for decimals:
{% raw %}{% assign rate = "3.75" | plus: 0 %} → 3.75 (preserves decimal){% assign rate = "3.75" | times: 1 %} → 3 (integer multiplication — truncates!){% assign rate = "3.75" | times: 1.0 %} → 3.75 (decimal multiplication — preserves){% endraw %}💡 Rule of thumb:
- Use
| plus: 0for most conversions — it handles both integers and decimals - Use
| times: 1.0when you want to be explicit about preserving decimals - Use
| times: 1only when you intentionally want integer truncation
Always pair with default to handle missing fields:
{% raw %}{% assign score = health_score | default: 0 | plus: 0 %}{% assign rate = growth_rate | default: 0 | times: 1.0 %}{% endraw %}Quick Reference — All Number Filters
Section titled “Quick Reference — All Number Filters”| Filter | What It Does | Example |
|---|---|---|
plus: n |
Add (also converts string → number) | "95000" | plus: 0 → 95000 |
minus: n |
Subtract | 100 | minus: 25 → 75 |
times: n |
Multiply (also converts string → number) | "5" | times: 1 → 5 |
divided_by: n |
Divide | 100 | divided_by: 4 → 25 |
modulo: n |
Remainder after division | 10 | modulo: 3 → 1 |
round: n |
Round to n decimal places | 4.678 | round: 2 → 4.68 |
floor |
Round down to nearest integer | 4.9 | floor → 4 |
ceil |
Round up to nearest integer | 4.1 | ceil → 5 |
abs |
Remove the negative sign | -5 | abs → 5 |
Now let’s explore each one.
plus — Addition and String-to-Number Conversion
Section titled “plus — Addition and String-to-Number Conversion”What is it?
Section titled “What is it?”The plus filter adds a number to the value. When applied to a string, it converts the string to a number first and then adds. This dual behavior makes plus: 0 the most common way to convert strings to numbers — adding zero changes nothing mathematically but forces the type conversion.
Excel analogy:
plus: 0is like=A1 + 0or=VALUE(A1)in Excel — a trick to force a text-formatted cell into a real number.
Why it matters in Cast
Section titled “Why it matters in Cast”plus is both your primary addition tool and your primary conversion tool. You’ll use | plus: 0 at the top of nearly every narration that involves numeric data.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 10 | plus: 5 }} → 15{{ "95000" | plus: 0 }} → 95000 (string converted to number){{ "95000" | plus: 5000 }} → 100000 (converted AND added){% endraw %}Real Cast example
Section titled “Real Cast example”{% raw %}{%- assign score = health_score | default: 0 | plus: 0 -%}{%- assign bonus = 5 -%}{%- assign adjusted = score | plus: bonus -%}
Your health score is {{ score }}. With this quarter's engagement bonus,your adjusted score is {{ adjusted }}.{% endraw %}If health_score is "78":
Output: Your health score is 78. With this quarter's engagement bonus, your adjusted score is 83.
Common mistakes
Section titled “Common mistakes”❌ Adding two unconverted strings:
{% raw %}{% assign total = arr | plus: upsell_value %}{% endraw %}If both are strings, the behavior is unpredictable.
✅ Convert both first, then add:
{% raw %}{%- assign arr_val = arr | default: 0 | plus: 0 -%}{%- assign upsell = upsell_value | default: 0 | plus: 0 -%}{%- assign total = arr_val | plus: upsell -%}{% endraw %}minus — Subtraction
Section titled “minus — Subtraction”What is it?
Section titled “What is it?”The minus filter subtracts a number from the value. Like plus, it converts strings to numbers implicitly, but explicit conversion is always safer.
Excel analogy:
=A1 - B1
Why it matters in Cast
Section titled “Why it matters in Cast”Use minus for calculating differences — how much a metric changed, how many days until renewal, how much capacity remains.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 100 | minus: 25 }} → 75{{ "200" | minus: 50 }} → 150{% endraw %}Real Cast example
Section titled “Real Cast example”{% raw %}{%- assign reserved = LicenseReserved | default: 0 | plus: 0 -%}{%- assign used = LicenseUsed | default: 0 | plus: 0 -%}{%- assign remaining = reserved | minus: used -%}
You have {{ remaining }} licenses remaining out of {{ reserved }} reserved.{% endraw %}If LicenseReserved is "100" and LicenseUsed is "73":
Output: You have 27 licenses remaining out of 100 reserved.
Common mistakes
Section titled “Common mistakes”❌ Getting the subtraction order backwards:
{% raw %}{%- assign change = previous | minus: current -%}{% endraw %}If you want how much something increased, subtract the old from the new, not the other way around.
✅ New minus old for increase:
{% raw %}{%- assign increase = current | minus: previous -%}{% endraw %}times — Multiplication and Conversion
Section titled “times — Multiplication and Conversion”What is it?
Section titled “What is it?”The times filter multiplies the value by a number. Like plus, it also converts strings to numbers, making | times: 1 another conversion option.
Excel analogy:
=A1 * B1
Why it matters in Cast
Section titled “Why it matters in Cast”Use times for percentage calculations, scaling values, and converting between units. The times: 1.0 pattern is critical when working with decimal values like growth rates or utilization percentages.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 10 | times: 5 }} → 50{{ "5" | times: 1 }} → 5 (integer conversion){{ "3.75" | times: 1.0 }} → 3.75 (decimal-preserving conversion){{ 0.75 | times: 100 }} → 75 (decimal to percentage){% endraw %}Real Cast example
Section titled “Real Cast example”{% raw %}{%- assign used = LicenseUsed | default: 0 | times: 1.0 -%}{%- assign reserved = LicenseReserved | default: 1 | times: 1.0 -%}{%- assign pct = used | divided_by: reserved | times: 100 | round: 0 -%}
Your license utilization is {{ pct }}%.{% endraw %}If LicenseUsed is "45" and LicenseReserved is "60":
used= 45.0,reserved= 60.045.0 / 60.0= 0.750.75 * 100= 75.0round: 0→ 75
Output: Your license utilization is 75%.
Common mistakes
Section titled “Common mistakes”❌ Using times: 1 with a decimal string:
{% raw %}{% assign rate = "3.75" | times: 1 %} → 3 (decimal truncated!){% endraw %}✅ Use times: 1.0 to preserve decimals:
{% raw %}{% assign rate = "3.75" | times: 1.0 %} → 3.75{% endraw %}❌ Forgetting to convert strings before multiplying two variables:
{% raw %}{%- assign result = price | times: quantity -%}{% endraw %}If both are strings, this may produce wrong results.
✅ Convert first:
{% raw %}{%- assign p = price | default: 0 | times: 1.0 -%}{%- assign q = quantity | default: 0 | plus: 0 -%}{%- assign result = p | times: q -%}{% endraw %}divided_by — Division
Section titled “divided_by — Division”What is it?
Section titled “What is it?”The divided_by filter divides the value by a number. The critical subtlety: integer division drops the decimal part unless you use a decimal divisor.
Excel analogy:
=A1 / B1— except Excel always gives you a decimal result, while Liquid only does if at least one side is a decimal.
Why it matters in Cast
Section titled “Why it matters in Cast”Division is essential for percentages, averages, per-unit calculations, and benchmarking. The integer-vs-decimal distinction is one of the most common gotchas in Cast arithmetic.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 100 | divided_by: 4 }} → 25{{ 100 | divided_by: 3 }} → 33 (integer division — remainder dropped){{ 100 | divided_by: 3.0 }} → 33.333... (decimal division — fraction preserved){{ 10 | divided_by: 4.0 }} → 2.5{% endraw %}The rule is simple: if the divisor is a whole number (3), you get integer division. If the divisor has a decimal point (3.0), you get decimal division.
Real Cast example
Section titled “Real Cast example”Calculating a percentage correctly:
{% raw %}{%- assign current = analysis_current_qtr_avg | default: 0 | times: 1.0 -%}{%- assign previous = analysis_prev_qtr_avg | default: 0 | times: 1.0 -%}
{%- if previous > 0 -%} {%- assign change_pct = current | minus: previous | divided_by: previous | times: 100 | round: 1 -%}
{%- if change_pct > 0 -%} Usage increased {{ change_pct }}% compared to last quarter. {%- elsif change_pct < 0 -%} Usage decreased {{ change_pct | abs }}% compared to last quarter. {%- else -%} Usage remained the same as last quarter. {%- endif -%}{%- endif -%}{% endraw %}Notice | times: 1.0 on the initial conversions — this ensures that when we later divide current by previous, we get decimal division, not integer division.
Common mistakes
Section titled “Common mistakes”❌ Getting zero from a percentage calculation because of integer division:
{% raw %}{%- assign used = 45 -%}{%- assign total = 60 -%}{%- assign pct = used | divided_by: total | times: 100 -%}{{ pct }} → 0 (because 45 / 60 = 0 in integer division, then 0 * 100 = 0){% endraw %}✅ Ensure decimal division:
{% raw %}{%- assign used = 45 -%}{%- assign total = 60.0 -%}{%- assign pct = used | divided_by: total | times: 100 | round: 0 -%}{{ pct }} → 75{% endraw %}Or convert both to decimals earlier with | times: 1.0.
❌ Dividing by zero:
{% raw %}{%- assign pct = used | divided_by: reserved -%}{% endraw %}If reserved is 0, this causes an error.
✅ Always guard against division by zero:
{% 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 %}modulo — Remainder After Division
Section titled “modulo — Remainder After Division”What is it?
Section titled “What is it?”The modulo filter returns the remainder when dividing by a number. If you divide 10 by 3, the answer is 3 with a remainder of 1 — modulo gives you that 1.
Excel analogy: Identical to
=MOD(A1, 3).
Why it matters in Cast
Section titled “Why it matters in Cast”modulo is used in several Cast snippet patterns: the Avatar Name Generator uses it to pick a name from a list deterministically, and the Customer Since snippet uses it to calculate remaining days after dividing by weeks or months.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 10 | modulo: 3 }} → 1 (10 ÷ 3 = 3 remainder 1){{ 12 | modulo: 4 }} → 0 (12 ÷ 4 = 3 remainder 0){{ 7 | modulo: 2 }} → 1 (odd number — remainder 1 when divided by 2){% endraw %}Real Cast example
Section titled “Real Cast example”Picking a name from a list based on contact ID (from the Avatar Name Generator):
{% raw %}{%- assign names = "Amy,Bella,Chloe,Diana,Emily" | split: "," -%}{%- assign contactId = contact_id | default: 1 | plus: 0 -%}{%- assign nameIndex = contactId | modulo: names.size -%}{{ names[nameIndex] }}{% endraw %}If contact_id is "7" and there are 5 names:
7 modulo 5= 2names[2]="Chloe"
The contact always gets the same name (deterministic), and it wraps around no matter how large the contact ID is.
Common mistakes
Section titled “Common mistakes”❌ Confusing modulo with divided_by:
{% raw %}{{ 10 | modulo: 3 }} → 1 (the remainder){{ 10 | divided_by: 3 }} → 3 (the quotient){% endraw %}They’re complementary — together they fully describe a division: 10 ÷ 3 = 3 remainder 1.
round — Round to n Decimal Places
Section titled “round — Round to n Decimal Places”What is it?
Section titled “What is it?”The round filter rounds a number to a specified number of decimal places. Without an argument, it rounds to the nearest whole number.
Excel analogy: Identical to
=ROUND(A1, 2).
Why it matters in Cast
Section titled “Why it matters in Cast”Percentages, averages, and financial calculations often produce long decimal results like 73.333333. Rounding presents clean, professional numbers in your narration.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 4.678 | round: 2 }} → 4.68{{ 4.678 | round: 1 }} → 4.7{{ 4.678 | round: 0 }} → 5{{ 4.678 | round }} → 5 (default: 0 decimal places){{ 4.5 | round }} → 5 (rounds up at .5){% endraw %}Real Cast example
Section titled “Real Cast example”{% raw %}{%- assign current = analysis_current_qtr_avg | default: 0 | times: 1.0 -%}{%- assign previous = analysis_prev_qtr_avg | default: 1 | times: 1.0 -%}{%- assign change = current | minus: previous | divided_by: previous | times: 100 | round: 1 -%}
Your usage changed by {{ change }}% compared to last quarter.{% endraw %}Output: Your usage changed by 12.5% compared to last quarter. (not 12.4999999)
Common mistakes
Section titled “Common mistakes”❌ Rounding before doing all your math:
{% raw %}{%- assign ratio = used | divided_by: total | round: 2 -%}{%- assign pct = ratio | times: 100 -%}{{ pct }} → might produce 75.0 or imprecise results due to early rounding{% endraw %}✅ Do all math first, round last:
{% raw %}{%- assign pct = used | divided_by: total | times: 100 | round: 1 -%}{{ pct }}{% endraw %}floor — Round Down to Integer
Section titled “floor — Round Down to Integer”What is it?
Section titled “What is it?”The floor filter always rounds down to the nearest whole number, regardless of the decimal part. 4.1 and 4.9 both become 4.
Excel analogy: Identical to
=FLOOR(A1, 1)or=INT(A1).
Why it matters in Cast
Section titled “Why it matters in Cast”Use floor when you need a conservative (lower) estimate — for example, calculating whole months remaining on a contract or complete units consumed.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 4.1 | floor }} → 4{{ 4.9 | floor }} → 4{{ 5.0 | floor }} → 5{{ -4.1 | floor }} → -5 (rounds toward negative infinity){% endraw %}Real Cast example
Section titled “Real Cast example”{% raw %}{%- assign days = diffDays | default: 0 | plus: 0 -%}{%- assign whole_months = days | divided_by: 30.0 | floor -%}
You have at least {{ whole_months }} full months until renewal.{% endraw %}If there are 95 days remaining: 95 / 30.0 = 3.166…, floor → 3.
Output: You have at least 3 full months until renewal.
ceil — Round Up to Integer
Section titled “ceil — Round Up to Integer”What is it?
Section titled “What is it?”The ceil filter always rounds up to the nearest whole number, regardless of the decimal part. 4.1 and 4.01 both become 5.
Excel analogy: Identical to
=CEILING(A1, 1)or=ROUNDUP(A1, 0).
Why it matters in Cast
Section titled “Why it matters in Cast”Use ceil when you need a generous (upper) estimate — for example, calculating how many licenses a team needs (you can’t buy half a license) or presenting a percentage change where rounding down would understate the situation.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ 4.1 | ceil }} → 5{{ 4.9 | ceil }} → 5{{ 5.0 | ceil }} → 5{{ -4.9 | ceil }} → -4 (rounds toward zero){% endraw %}Real Cast example
Section titled “Real Cast example”From the benchmarking pattern in the prompt:
{% raw %}{%- assign current = analysis_current_qtr_avg | default: 0 | times: 1.0 -%}{%- assign previous = analysis_prev_qtr_avg | default: 0 | times: 1.0 -%}
{%- if current > previous -%} {%- assign pct_increase = current | minus: previous | divided_by: previous | times: 100 | ceil -%} Usage increased by at least {{ pct_increase }}% from the previous quarter.{%- endif -%}{% endraw %}Using ceil here ensures the stated percentage is never lower than the actual value — a safe choice when highlighting positive change.
abs — Absolute Value
Section titled “abs — Absolute Value”What is it?
Section titled “What is it?”The abs filter removes the negative sign from a number, returning its absolute (positive) value. Positive numbers and zero are unaffected.
Excel analogy: Identical to
=ABS(A1).
Why it matters in Cast
Section titled “Why it matters in Cast”When you calculate a difference (current minus previous), the result may be negative. If your narration says “usage decreased by X%”, you want to show 15, not -15. abs strips the negative sign so you can word the sentence naturally.
Liquid syntax
Section titled “Liquid syntax”{% raw %}{{ -5 | abs }} → 5{{ 5 | abs }} → 5{{ -3.7 | abs }} → 3.7{% endraw %}Real Cast example
Section titled “Real Cast example”{% raw %}{%- assign current = analysis_current_qtr_avg | default: 0 | times: 1.0 -%}{%- assign previous = analysis_prev_qtr_avg | default: 0 | times: 1.0 -%}
{%- if previous > 0 -%} {%- if current < previous -%} {%- assign decrease = previous | minus: current | divided_by: previous | times: 100 | ceil | abs -%} Your average monthly usage decreased from the previous quarter by {{ decrease }}%. {%- endif -%}{%- endif -%}{% endraw %}Without abs, the narration might say “decreased by -15%” which reads awkwardly. With abs, it cleanly says “decreased by 15%.”
Common mistakes
Section titled “Common mistakes”❌ Forgetting abs when the subtraction order might produce a negative:
{% raw %}{%- assign diff = current | minus: previous -%}Usage changed by {{ diff }}%. → "Usage changed by -15%."{% endraw %}✅ Use abs and handle the sign in the wording:
{% raw %}{%- assign diff = current | minus: previous -%}{%- if diff >= 0 -%} Usage increased by {{ diff }}%.{%- else -%} Usage decreased by {{ diff | abs }}%.{%- endif -%}{% endraw %}Putting It All Together — The Complete Numeric Pipeline
Section titled “Putting It All Together — The Complete Numeric Pipeline”Here’s the pattern you’ll follow for virtually every numeric calculation in Cast:
{% raw %}Step 1: Default → handle nil/empty fieldsStep 2: Convert → string to number (plus: 0 or times: 1.0)Step 3: Calculate → arithmetic filters (plus, minus, times, divided_by)Step 4: Format → round, floor, ceil, absStep 5: Display → output with context{% endraw %}Complete example — license utilization with benchmarking:
{% raw %}{% comment %} Step 1 & 2: Safe defaults and conversion {% endcomment %}{%- assign used = LicenseUsed | default: 0 | times: 1.0 -%}{%- assign reserved = LicenseReserved | default: 0 | times: 1.0 -%}{%- assign benchmark = avgUsage | default: 0 | times: 1.0 -%}
{% comment %} Step 3 & 4: Calculate and format {% endcomment %}{%- if reserved > 0 -%} {%- assign pct = used | divided_by: reserved | times: 100 | round: 0 -%}{%- else -%} {%- assign pct = 0 -%}{%- endif -%}
{% comment %} Step 5: Display with context {% endcomment %}You used {{ used | round: 0 }} of {{ reserved | round: 0 }} reserved licenses({{ pct }}% utilization).
{%- if benchmark > 0 and reserved > 0 -%} {%- assign diff = pct | minus: benchmark | round: 0 -%} {%- if diff > 0 %} That's {{ diff }}% above the industry average. {%- elsif diff < 0 %} That's {{ diff | abs }}% below the industry average. {%- else %} That's right at the industry average. {%- endif -%}{%- endif -%}{% endraw %}Try It Yourself — Module 5 Capstone Exercise
Section titled “Try It Yourself — Module 5 Capstone Exercise”Exercise: You have three CRM fields:
arr="320000"(string)growth_rate="0.15"(string, represents 15%)previous_arr="280000"(string)
Write Liquid that:
- Converts all three to numbers (preserving the decimal in
growth_rate) - Calculates the projected ARR:
arr * (1 + growth_rate) - Calculates the year-over-year change percentage:
(arr - previous_arr) / previous_arr * 100 - Displays both results, with the projected ARR rounded to the nearest whole number and the change percentage rounded to one decimal place
Click to reveal the answer
{% raw %}{%- comment -%} Convert strings to numbers {%- endcomment -%}{%- assign arr_val = arr | default: 0 | plus: 0 -%}{%- assign growth = growth_rate | default: 0 | times: 1.0 -%}{%- assign prev_arr_val = previous_arr | default: 0 | plus: 0 -%}
{%- comment -%} Projected ARR: arr * (1 + growth_rate) {%- endcomment -%}{%- assign multiplier = 1.0 | plus: growth -%}{%- assign projected = arr_val | times: multiplier | round: 0 -%}
{%- comment -%} YoY change: (current - previous) / previous * 100 {%- endcomment -%}{%- if prev_arr_val > 0 -%} {%- assign yoy_change = arr_val | minus: prev_arr_val | divided_by: prev_arr_val | times: 100.0 | round: 1 -%}{%- else -%} {%- assign yoy_change = 0 -%}{%- endif -%}
Your current ARR of ${{ arr_val }} represents a {{ yoy_change }}% increase year over year.At your current growth rate of {{ growth | times: 100 | round: 0 }}%, your projected ARRnext year is ${{ projected }}.{% endraw %}Output:
{% raw %}Your current ARR of $320000 represents a 14.3% increase year over year.At your current growth rate of 15%, your projected ARR next year is $368000.{% endraw %}Key details:
times: 1.0preserves the decimal ingrowth_ratedivided_by: prev_arr_valworks as decimal division because we converted withtimes: 1.0where needed {% raw %}- Division by zero is guarded with ``{% endraw %}- Rounding happens at the very end, after all math is done
What’s Next
Section titled “What’s Next”You can now handle any number that comes your way in Cast. In Module 6, you’ll learn the list and array filters — sorting, deduplicating, joining, and extracting items from arrays to build dynamic content from multi-value data.
📖 Official documentation:
- Standard Filters: https://school.cast.app/liquid/liquid-filters.html
- Snippet Library (numeric patterns): https://school.cast.app/liquid/liquid-library.html