Computed Fields
Computed fields calculate themselves from an expression. You write the expression once, and the renderer keeps the answer up to date as the other fields on the file change.
Computed fields are read-only, so you don't edit them directly. They sit next to your other fields in the view and show the current result. Their values are never stored in the file — they're recalculated every time the file is read.
Expressions may look scary, but they are usually easier than you think. If you feel stuck, you can always ask Orbit to help you write one.
When to use a computed field
-
Derived stats: a Dex modifier from a Dexterity score, AC from base + bonuses, proficiency bonus from level.
-
Totals and tallies: total weight from an inventory, sum of bonuses from a set of effects.
-
Conditional text or state: "Alive" vs. "Dead" based on hit points, "Encumbered" once carried weight crosses a limit.
Setting one up
In the file type editor, add a field and pick Computed as the type. Then choose what kind of value the expression produces — Number, Text, Boolean, List, or Object — and write the expression.
For List and Object outputs, a Configure card appears under the output type where you can declare the shape of the value: what type each list item is (text, number, boolean, or an object with its own typed properties), or which properties the computed object carries. Nested objects can be typed too. Declaring the shape is optional, but it pays off — layouts can bind the declared fields (a table or repeated tracker can show each item's parts), and autocomplete offers them when other expressions read this field.
The expression editor helps as you type: $self. suggests your
fields, load( suggests your reference fields and then the target
file's fields, and starting a function name suggests the built-in library.
Below the editor, the Fields, References, Functions, and Examples tabs let you
browse everything available and insert it with a click.
Expression basics
Use $self to read the file's own fields, and use the usual math
operators:
$self.dexterity - 10
($self.dexterity - 10) / 2
floor(($self.dexterity - 10) / 2)
Dot syntax reaches into nested fields on objects:
$self.stats.dexterity
floor(($self.stats.dexterity - 10) / 2) + $self.level
A missing field reads as null rather than an error. Use
?? to supply a fallback:
($self.hp ?? 0) + ($self.tempHp ?? 0)
Append .length to a list field to get its number of items, or to
a text field to get its number of characters:
$self.inventory.length
$self.name.length > 20 ? "Long name" : "Short name"
Ternary expressions handle if/else:
$self.hp > 0 ? "Alive" : "Dead"
Backtick strings build text from values (a Text output type pairs well with these):
`Level ${$self.level ?? 1} ${$self.class ?? ""}`
Using computed fields in other expressions
Computed fields can read other computed fields on the same file. This is useful when you want to build small expressions first, then reuse them in larger ones.
For example, a Character file type might have a dexModifier
expression:
floor((($self.dexterity ?? 10) - 10) / 2)
Then an unarmoredArmorClass expression can use it:
10 + $self.dexModifier
Avoid circular expressions. A computed field must not depend on itself, even through another computed field.
If you rename a field that other expressions read, the editor notices and offers to update those expressions to the new name in one click.
Functions you can use
|
Function |
What it does |
|---|---|
|
|
Rounds down, up, or to the nearest whole number. |
|
|
Absolute value, square root, and powers. |
|
|
Smallest or largest of the values. Works with a single list, too. |
|
|
Keeps a number between two bounds. |
|
|
Adds a list's numbers. An optional second argument computes each
item's contribution: |
|
|
Average of a list's numbers. Also takes the optional per-item function. |
|
|
How many items a list has (empty values excluded). With a function,
how many items match: |
|
|
Converts between types. Arithmetic never converts text on its own, so
use |
|
|
An object's keys or values as a list, its
|
|
|
Resolves a reference field's value to the referenced file's content. |
Methods you can call on values
Lists and text also have methods, called with a dot on the value:
-
On lists:
map, filter, find, findIndex, some, every, includes, indexOf, at, slice, concat, flat, flatMap, join, reduce, toSorted, toReversed, unique— andat(-1)is the last item. -
On text:
toUpperCase, toLowerCase, trim, includes, startsWith, endsWith, slice, split, replace, replaceAll, padStart, padEnd.
$self.inventory.filter(e => e.equipped).map(e => e.name).join(", ")
$self.skills.toSorted((a, b) => b.rank - a.rank).at(0)?.name
$self.name.toUpperCase()
Functions passed to methods (e => …) are written inline like
the examples above — a name for each item, an arrow, and an expression.
Reading values from referenced files
Expressions can reach across a
reference field
and pull a value out of the file on the other end. Pass the reference field's
value to load(...):
The target field can be a normal field or a computed field. If your Armor file
type has a field called totalAcBonus, a Character expression can
read it like this:
10 + floor((($self.dexterity ?? 10) - 10) / 2) + (load($self.armor)?.totalAcBonus ?? 0)
This reads as "ten, plus my Dex modifier from Dexterity, plus whichever
totalAcBonus is on the armor I'm wearing." Swap the equipped
armor and the result updates automatically.
Cross-file reads go one level deep: the file you load(...) can
have computed fields of its own, but those must not use
load(...) themselves. An unresolved reference reads as
null, so guard reads with ?. and ??
like the examples here.
Reading across a list of references
When references live inside list rows, use a per-item function with helpers
such as sum, map, min, or
max:
sum($self.inventory, e => (load(e.item)?.weight ?? 0) * (e.quantity ?? 1))
max($self.activeEffects.map(e => load(e)?.bonus ?? 0))
Referenced computed fields work in lists too:
sum($self.equipment, e => load(e)?.totalWeight ?? 0)
To keep the values themselves instead of collapsing them to one number, give
the expression a List output type. A
preparedSpellSchools expression like the one below produces a
list your layout can render as badges, and .join(...) flattens
any list to a single line of text:
$self.preparedSpells.map(s => load(s)?.school).filter(s => s).unique()
$self.party.map(p => load(p)?.$name).join(", ")
An end-to-end example: armor-driven AC
-
Create an Armor file type with a number field called
acBonus. -
On the character file type, add a Reference field called
armorthat targets Armor. -
Add a Computed field called
armorClasswith this expression and a Number output type:
10 + floor((($self.dexterity ?? 10) - 10) / 2) + (load($self.armor)?.acBonus ?? 0)
Now any character can equip an armor, and their AC adjusts to that armor's
bonus. Replace the single reference with a list of equipped items and switch
to sum($self.equipment, e => load(e)?.acBonus ?? 0) if more
than one piece can stack.
Tips
-
If a referenced field is missing or empty, the expression treats it as
null, and most operators stay quiet rather than throwing errors. -
If a computed field reads another computed field, Craft uses the calculated value of the other field.
-
Avoid circular expressions. A computed field should not depend on itself directly or through another computed field.
-
Computed fields are never required. They're calculated, not entered.
-
Don't write a computed field's value into a data file — it will be ignored and recalculated. To change the result, change the fields the expression reads.
-
When editing schemas from a cloned workspace, run
craft checkafter expression edits — thefile-typescheck runs the same validation the server uses at push, andcomputed-field-valuesflags stored values that would be stripped.