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 32 functions
Section titled “All 32 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") |
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(is_active, "Active", "Inactive") |
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.
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.