Craft

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

floor(n) / ceil(n) / round(n)

Rounds down, up, or to the nearest whole number.

abs(n) / sqrt(n) / pow(base, exponent)

Absolute value, square root, and powers.

min(a, b, …) / max(a, b, …)

Smallest or largest of the values. Works with a single list, too.

clamp(value, low, high)

Keeps a number between two bounds.

sum(list)

Adds a list's numbers. An optional second argument computes each item's contribution: sum($self.rows, e => e.qty).

avg(list)

Average of a list's numbers. Also takes the optional per-item function.

count(list)

How many items a list has (empty values excluded). With a function, how many items match: count($self.quests, q => q.done) .

number(x) / string(x) / boolean(x)

Converts between types. Arithmetic never converts text on its own, so use number(...) when a value might arrive as text.

keys(obj) / values(obj) / entries(obj) / has(obj, key)

An object's keys or values as a list, its [key, value] pairs, or whether a key is present.

load(value)

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 — and at(-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

  1. Create an Armor file type with a number field called acBonus.

  2. On the character file type, add a Reference field called armor that targets Armor.

  3. Add a Computed field called armorClass with 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 check after expression edits — the file-types check runs the same validation the server uses at push, and computed-field-values flags stored values that would be stripped.