Formulas
A formula is an expression like add(strength, intelligence) or years_since(birth_date) that produces a value from this entity’s other fields. The system computes the result whenever the entity is shown.
Formulas are the most flexible kind of auto-calculated field. You can do maths, string formatting, date arithmetic, and conditional logic — all from a small set of built-in functions.
Writing a formula
Section titled “Writing a formula”Formulas live in the Formula field of the auto-calculate behaviour editor. The editor shows you a function picker on the right; click any function to insert it at the cursor.
The shape is always the same: a function name, an opening parenthesis, arguments separated by commas, and a closing parenthesis.
add(strength, intelligence)multiply(base_value, 2)years_since(birth_date)You can nest functions inside each other:
divide(add(a, b), 2) // average of a and bround(multiply(price, 1.07)) // price plus 7%, roundedTo reference a field on this entity, just type its identifier — strength, birth_date, etc. (No braces, no quotes.)
For text values, wrap them in quotes:
concat(first_name, " ", last_name)For numbers, type them as-is: 2, 0.5, -7.
Live syntax checking
Section titled “Live syntax checking”The editor checks your formula as you type. You’ll see one of three things under the formula box:
- Nothing — looks fine.
- “Checking syntax…” — the editor is asking the server.
- A red error message — describes what’s wrong (e.g. “Unknown function: bogus” or “Unbalanced parentheses”). Save is blocked until you fix it.
The check runs about half a second after you stop typing, so quick edits don’t spam the server.
Common patterns
Section titled “Common patterns”A few formulas you’ll reach for again and again:
| Goal | Formula |
|---|---|
| Sum of two stats | add(strength, intelligence) |
| Difference | subtract(max_health, current_health) |
| Percentage | multiply(divide(current, max), 100) |
| Round a result | round(divide(total, count)) |
| Pick one of two values | if(is_active, bonus, 0) |
| First non-empty | coalesce(nickname, first_name, "Unknown") |
| Years between dates | years_since(birth_date) |
| Days between dates | days_between(start_date, end_date) |
All 41 functions
Section titled “All 41 functions”Functions are grouped by what they do. Click into the editor’s function picker to insert any of them with placeholder arguments.
| Function | What it does | Example |
|---|---|---|
add(a, b, ...) | Add two or more numbers. | add(strength, intelligence) |
subtract(a, b) | First minus second. | subtract(max_hp, current_hp) |
multiply(a, b, ...) | Multiply two or more numbers. | multiply(price, quantity) |
divide(a, b) | First divided by second. | divide(total_xp, level) |
power(base, exponent) | Raise a number to a power. | power(level, 2) |
sqrt(x) | Square root. | sqrt(damage) |
abs(x) | Absolute value. | abs(temperature_delta) |
Rounding
Section titled “Rounding”| Function | What it does | Example |
|---|---|---|
round(x) | Round to nearest whole number. | round(average) |
floor(x) | Round down. | floor(divide(xp, 1000)) |
ceil(x) | Round up. | ceil(required_materials) |
mod(a, b) | Remainder after division. | mod(day_number, 7) |
Picking the largest, smallest, or summary
Section titled “Picking the largest, smallest, or summary”| Function | What it does | Example |
|---|---|---|
min(a, b, ...) | Smallest of the inputs. | min(health, stamina, mana) |
max(a, b, ...) | Largest of the inputs. | max(base_dmg, modified_dmg) |
sum(a, b, ...) | Add all the inputs. | sum(str, dex, con, int, wis, cha) |
avg(a, b, ...) | Average of the inputs. | avg(test1, test2, test3) |
count(a, b, ...) | How many of the inputs aren’t empty. | count(skill1, skill2, skill3) |
| Function | What it does | Example |
|---|---|---|
concat(a, b, ...) | Stick strings together. | concat(first_name, " ", last_name) |
upper(s) | Make uppercase. | upper(name) |
lower(s) | Make lowercase. | lower(email) |
trim(s) | Remove whitespace from start and end. | trim(user_input) |
length(s) | Number of characters. | length(description) |
substring(s, start, end) | Take a slice from index start up to index end. | substring(code, 0, 3) |
replace(s, old, new) | Replace text in a string. | replace(text, "old", "new") |
| Function | What it does | Example |
|---|---|---|
years_since(date) | Years from the date to now. | years_since(birth_date) |
days_between(a, b) | Days between two dates. | days_between(start, end) |
date_add_days(date, n) | Add days to a date. | date_add_days(start, 30) |
date_add_months(date, n) | Add months to a date. | date_add_months(founded, 6) |
date_format(date, fmt) | Format the date as a string. | date_format(birth_date, "%Y-%m-%d") |
Comparisons
Section titled “Comparisons”| Function | What it does | Example |
|---|---|---|
greater_than(a, b) | True when a is greater than b. | greater_than(worshipper_count, 0) |
less_than(a, b) | True when a is less than b. | less_than(age, 18) |
greater_or_equal(a, b) | True when a is at least b. | greater_or_equal(level, 5) |
less_or_equal(a, b) | True when a is at most b. | less_or_equal(temperature, 0) |
equals(a, b) | True when a equals b (strict — 5 is not equal to "5"). | equals(status, "active") |
not_equals(a, b) | True when a and b differ. | not_equals(status, "deleted") |
Combine boolean values — or any values, since these treat 0, "", empty, and missing as false and anything else as true.
| Function | What it does | Example |
|---|---|---|
and(a, b, ...) | True when every argument is true. Two or more arguments. | and(greater_or_equal(age, 18), equals(status, "active")) |
or(a, b, ...) | True when any argument is true. Two or more arguments. | or(equals(role, "admin"), equals(role, "owner")) |
not(value) | Flip true to false and back. | not(is_null(email)) |
Conditionals and missing values
Section titled “Conditionals and missing values”| Function | What it does | Example |
|---|---|---|
if(condition, then, else) | Pick one of two values based on a condition. | if(greater_than(hp, 0), "alive", "dead") |
coalesce(a, b, ...) | First value that isn’t empty. | coalesce(nickname, first_name, "Unknown") |
is_null(x) | True when the value is empty. | is_null(optional_field) |
is_not_null(x) | True when the value isn’t empty. | is_not_null(required_field) |
Worked examples
Section titled “Worked examples”A handful of complete formulas, with a note on what each one is for.
Computed age
Section titled “Computed age”You have a birth_date field, and you want an age field that updates as the in-world calendar advances:
years_since(birth_date)The result is a whole number. Set the age field to read-only.
Full name with optional title
Section titled “Full name with optional title”Characters might or might not have a title. You want display_name to read “Lord Tyrell Mormont” when there is one, “Tyrell Mormont” when there isn’t:
concat(coalesce(title, ""), if(is_null(title), "", " "), first_name, " ", last_name)A bit hairy. The cleaner answer is usually a Template instead — formulas are great for maths, less elegant for variable text-stitching.
Effective health
Section titled “Effective health”Max HP minus damage taken, with a floor at zero:
max(0, subtract(max_hp, damage_taken))Status from a flag
Section titled “Status from a flag”When a Yes/No field is enough to decide the value:
if(is_active, "Active", "Inactive")if takes a true/false value as its first argument and returns one of two results. For a Yes/No field, use the field directly. For “is this empty”, use is_null or is_not_null. For numeric ranges or specific values, wrap the condition in a comparison function.
Status from a count
Section titled “Status from a count”When you want a boolean derived from a calculated count — for example, an is_worshipped flag that’s true whenever a deity has at least one worshipper:
greater_than(worshipper_count, 0)This is the standard shape for “is X populated”: pair a *_count aggregation with greater_than(..., 0). The result is true / false / empty — empty when the count itself hasn’t been computed yet, which keeps the difference between “no worshippers” and “we haven’t checked” intact.
Eligibility from multiple checks
Section titled “Eligibility from multiple checks”When several conditions all need to hold:
and(greater_or_equal(age, 18), equals(status, "active"), not(is_null(citizenship)))and accepts two or more arguments and evaluates them all. The companion or is true when any argument is true; not flips a single value. Combine these with the comparison functions to express any boolean condition you need.
Spell save DC
Section titled “Spell save DC”D&D-style:
add(8, proficiency_bonus, ability_modifier)What can go wrong
Section titled “What can go wrong”A few things the syntax checker won’t catch:
Field renamed or deleted. If the formula references strength and you later rename the field to str, the formula stops resolving. The result becomes empty until you update the formula. The Behaviour editor flags this when you open it.
Numeric field is empty. A formula like add(strength, intelligence) returns nothing if either field is empty. Use coalesce(strength, 0) to default to zero.
Division by zero or empty. divide(total, count) returns nothing when count is zero or empty. Wrap so the divide only runs with a usable value: if(coalesce(count, 0), divide(total, count), 0). The coalesce defaults count to 0 when it’s empty, and if treats 0 as false — so the divide only runs when count is non-zero.
Reference to itself. A formula that uses its own field (age = add(age, 1)) creates a cycle. The system rejects the save with an error.
For the full list of “I saved it but it doesn’t work” cases, see Common Pitfalls.