Skip to main content
Most WG content rows aren’t static text. They describe what they do to a character, and that description is a list of operations on the row’s operations column. When the character sheet builds, it walks every operation attached to every selected feat / ancestry / class / item / spell and applies them to a variables store. This page is the canonical reference for every operation type. The TypeScript schemas live in frontend/src/schemas/operations.ts; this page mirrors them in prose.

The shape

Every operation looks like this:
id is required by the engine for deduplication and result tracking. Never reuse one across operations. Content grants may nest, but a grant cannot lead back to the same content on its active ancestor path. Reusing a feat in separate branches is allowed. Cycles report the content IDs involved and fail the calculation; the sheet retains its last completed calculation. The engine also limits nesting to 64 levels, forward work to 100,000 operations across all passes, retained variable effects to 200,000, and removal reconstruction to 1,000,000 replay steps. These limits also apply when Web Workers are unavailable. Split unusually large homebrew operation graphs and correct self-grants instead of relying on partial calculation results.

Variables, briefly

Most operations read or write a variable (proficiencies, attributes, HP, perception, resistances, etc.). The set of named variables is defined in frontend/src/process/variables/variable-manager.ts, there are ~166 of them. Each variable has a type: When writing operations, always look up similar existing content first and copy the exact variable name and value shape. The engine fails silently on unknown variable names.

Variable operations

These read and write the variable store. Most operations on a typical feat or ancestry are these.

createValue

Introduce a new named variable on the character. Used for variables that aren’t in the default set (custom counters, archetype-specific bookkeeping).
If the variable already exists, this is a no-op. An active conditional branch can create its own variables. The selected branch’s declarations run before its proficiency guards and other effects; inactive branches create nothing. Recalculating after a level or selection change removes variables whose creating branch no longer runs.

setValue

Hard-set a variable. Wins over any prior value, including setValues from other content (last-write-wins).
Use sparingly: it stomps any other contribution. Prefer adjValue or addBonusToValue when stacking matters. LANGUAGE_NAMES and LANGUAGE_IDS are explicit final overrides. Set either to a JSON array of strings; the engine resolves the matching records and replaces both lists after normal grants and removals. An empty array removes all languages, including ancestry grants. Without an override, normal language operations retain their existing behavior.
Native array values are also supported. Unknown or ambiguous names, unknown IDs, and malformed lists produce an error without partially replacing either list. Use IDs when several sources define the same name. Deferred bindings and the final sheet see the replacement; conditionals still evaluate during their normal operation passes.

adjValue

Adjust a value. The exact behaviour depends on the variable type:
  • num: additive (+1, -2). Stacks with other adjValues on the same variable.
  • prof: increase a proficiency rank ('U' → 'T' → 'E' → 'M' → 'L'). Pass '1' to step up, '-1' to step down.
  • list-str: append. The string is the entry to add (e.g. "fire, 5" for resistances).
  • bool: set to true.
  • str: replace.
Skill adjustments resolve in earned-level order. Training and fixed-rank grants precede increases earned at the same level. An earlier increase consumed reaching expert cannot be counted again when a later feat grants master. Increasing the character’s level alone does not spend that old increase again. Numeric increases use their source’s level cap (master from 7, legendary from 15); explicit rank grants retain their authored rank. Active level-threshold conditions pass their activation level to descendant adjustments. Selector previews use this same progression at the selected occurrence, so a later feat cannot make an earlier valid choice appear disabled. Removing a grant rebuilds progression from the remaining occurrences. Self-guarded proficiency grants resolve before sibling conditional text that reads the resulting proficiency (including Medic’s Battle Medicine frequency). These mechanics do not invent missing content prerequisites.

addBonusToValue

A typed numeric bonus that respects PF2e bonus stacking rules (item / status / circumstance bonuses don’t stack with the same type). Use this rather than adjValue whenever the source describes a bonus type, and use it (with value: null) for any conditional or narrative note you want to surface beneath a stat without it being a literal numeric bonus. The three fields are value (number, or null for a non-numeric note), type ("item" / "status" / "circumstance" / "" for untyped), and text (free-form rider). For one check or damage roll, applicable modifier categories share one stacking calculation. The highest bonus and worst penalty of each type apply separately; untyped modifiers add together. For example, Frightened 1 and Stupefied 2 impose one status penalty of 2 on an Intelligence spell attack. Clumsy applies to Dexterity attacks, while Enfeebled applies to Strength attacks and damage. Neither automatically penalizes an Intelligence spell attack. Conditional text remains a displayed rider rather than an unconditional numeric modifier. Armor and an active invested potency rune form one item bonus to AC, which then competes with other item bonuses. Unburdened Iron ignores the armor speed penalty and reduces just one eligible other speed penalty by up to 5 feet, then applies normal typed stacking to the remaining penalties. Effective conditions use the strongest explicit or derived value regardless of selection order. Derived conditions do not replace their independently selected sources: removing Encumbered also removes its derived Clumsy 1, but keeps an independently selected Clumsy condition.

How it renders

WG composes value, type, and text into a single line shown beneath the stat. The shape of that line is what makes this operation feel different from adjValue: That last form is the workhorse for “show this note under the proficiency, but it isn’t a number”. Anything you want a player to see attached to a stat (situational rerolls, success-promotion clauses, “use this instead of normal rules” riders) goes in via value: null + type: "" + the prose in text.

Examples

bindValue

Bind one variable to another’s source so the bound value follows the original. Used for class DC inheritance, casting attribute mirroring, and a few other linked-stat patterns.
storeId is 'CHARACTER' for the active character; companion stores use their own IDs. Bindings resolve after ordinary operations and explicit language overrides. A chain reads each source’s final bound value regardless of authoring order. Multiple bindings to one target retain their authored assignment order and the variable’s existing setter rules: ordinary numeric values use the last assignment, while speeds and class HP per level retain the highest value. Self-copies do nothing. A dependency cycle between distinct variables fails the calculation with its variable path; the sheet retains its last completed state until the graph is corrected.

Granting and removing content

These attach (or detach) other database rows to the character.

giveAbilityBlock / removeAbilityBlock

Grant or revoke a feat, action, class feature, sense, heritage, mode, or physical feature by id. The type field here is the AbilityBlock subtype, not the row’s database type.
type is one of: 'action' | 'feat' | 'physical-feature' | 'sense' | 'class-feature' | 'heritage' | 'mode'. When granting Additional Lore, optional data.grantedLore specifies the Lore subject for that grant, for example "Elven Lore". Its existing progression applies at levels 3, 7, and 15. Each grant retains its own subject. Leaving the field empty preserves the feat’s normal choices, including any restrictions authored by its parent. Removal revokes the matching grants that have already run, including their nested grants, variable changes, typed bonuses, and deferred bindings or language overrides. Effects from independent sources remain, even if their equal or lower values were previously hidden. The same ownership applies to selected abilities, class features, and creature abilities. Removing a mode also deactivates it for this calculation. A later explicit grant can grant the content again. Removing a source that performed a removal also revokes that removal, restoring independent earlier grants. The engine rebuilds the remaining write intentions in order; it does not subtract the removed source’s final numeric difference. Conditions retain the normal three-pass evaluation order, rather than repeatedly reevaluating to a fixed point.

giveLanguage / removeLanguage

For a player-chosen language, use a select with optionType: 'LANGUAGE' instead.

giveSpell / removeSpell

Grant or revoke a single spell. giveSpell carries the metadata that determines how it shows up on the sheet (focus pool vs spell slot, casting source, fixed rank for innate spells).
removeSpell removes all existing grants of that spell ID, including each casting-source record in SPELL_DATA and innate spell proficiency granted by that spell. Other spells and independently granted casting proficiency remain. Names stay synchronized with IDs, including when two different spell records share a name. Spell-slot pools belong to their casting source and are not removed with an individual spell.

giveSpellSlot

Add spell slots to a casting source. Each slot is { lvl, rank, amt } where lvl is the character level the slot becomes available, rank the spell rank it casts, and amt the count (or null for unlimited / cantrip).

giveItem

Add an item to the character’s starting inventory.

giveTrait

Add a trait to the character itself. Rare; used when an ancestry or archetype literally makes you have a trait (e.g. humanoid). Successful grants also add the trait’s canonical name to the TRAIT_NAMES list variable for conditionals.

Branching and choices

conditional

Run nested operations only when conditions match. Conditions check variable state.
Operators: INCLUDES, NOT_INCLUDES, EQUALS, NOT_EQUALS, LESS_THAN, GREATER_THAN, GREATER_THAN_OR_EQUALS, LESS_THAN_OR_EQUALS. Multiple conditions are AND-ed; nest a second conditional inside trueOperations for OR semantics. Proficiency comparisons use U, T, E, M, or L. The editor saves its initial Untrained choice as U. Older proficiency checks with an empty string are interpreted as that same displayed default; other invalid rank values are not silently converted. Boolean comparisons use the strings TRUE and FALSE. Selecting a boolean variable or value type saves the editor’s initial True choice as TRUE. Existing empty or non-TRUE values retain their false comparison and display False; opening the editor does not silently change their meaning.

Source-qualified list checks

A list-str INCLUDES check can optionally match a typed amount from specified content categories instead of searching the aggregate text. The editor’s Match control defaults to Text. Type and amount enables Source categories. The qualifier uses the existing condition ID:
Each entry must reference exactly one existing list-str INCLUDES check and include at least one category. The reserved __proto__ ID cannot carry a qualifier; ordinary unqualified checks with that ID retain their existing behavior. A value such as fire, {{level/2}} matches the same damage type and numeric amount after the normal expression rounding. The nearest real content row owns the contribution; nested custom options keep that owner. Another granted row has its own owner. Duplicate contributions retain their independent sources, and the checked content cannot qualify itself. Contributions without a known content owner do not qualify. Qualified checks run after ordinary operations, language replacements and bindings settle. Ordinary checks in the same conditional keep their original evaluation timing. The engine evaluates all qualified checks before applying their branches. Unqualified conditions retain their existing behavior, and contribution ownership is execution-only, not saved character data. Qualified branches support isolated primitive writes, resolved ability grants and predefined custom selections. Nested conditionals, content removals, implicit casting/language/item/trait grants, filtered selections, skill-training discovery and paired language replacements are not supported in these branches. They also cannot change another qualifier’s inputs or discover additional qualified work. Unsupported or unresolved dependencies fail the calculation without replacing the last successful result. This mode is not a solver for mutually dependent rules.

select

Player-facing choice. Two modes: Predefined: you list the options inline.
Filtered: you describe a content query and the engine renders all matches.
optionType and the matching filter type must agree. Valid pairs: The chosen option id is recorded on character.operation_data.selections, keyed by the select operation’s id. A filtered additional Lore choice (ADJ_VALUE, group ADD-LORE) can explicitly list up to 16 unique sibling select UUIDs in optional data.selectionAliases, excluding its own UUID. This preserves already saved Lore choices when mutually exclusive branches raise their rank. The current operation’s saved key takes priority, including an empty saved choice; otherwise the first present alias in authored order is used. Lookup is limited to the same parent path, without name matching, transitive aliases or saved-key migration. Each Lore slot needs its own aliases. Clearing the choice removes all of its sibling keys at that parent, without clearing other slots or owners. Other selection kinds do not support aliases, and choices without aliases retain their existing behavior. The editor preserves this authored field for Lore edits without adding controls; changing to an unsupported selection kind removes it. An ABILITY_BLOCK filter can use excludedTraits to omit feats with any listed trait. It accepts trait IDs or names, like the required traits filter. A string level.max such as {{LEVEL/2}} is resolved when the character makes the selection. For a SPELL filter, rarity limits the available spells to one rarity. traditionFromSelection: { key, castingSource } uses the ability block saved at that selection key to find its unambiguous defineCastingSource tradition; if no tradition is found, the filter offers no spells. This lets a spell choice follow a selected patron before conditional casting-source grants run. For a weapon ADJ_VALUE filter, addToFamiliarity: true also applies the chosen weapon to WEAPON_FAMILIARITY, so a martial weapon uses simple-weapon proficiency or an advanced weapon uses martial-weapon proficiency. Set familiarityCategories: ["advanced"] when only advanced weapons should get that adjustment; omitting the category filter retains the existing behavior for every selected weapon. Ancestry feat filters also admit the existing All Ancestries trait, while preserving level, required-trait, and feat-type constraints. Archetype skill feats belong in skill feat choices: ordinary class feat and Free Archetype choices exclude them. A filter that explicitly requests the Skill trait can still select them.

Display and meta

These don’t change stats; they affect what the player sees.

injectText

Append prose to another piece of content’s description, conditionally. Used for archetype rewrites that say “instead of X, you can also Y” without forking the source row. Item injections appear in both catalog and inventory descriptions. They use the store of the character or companion whose drawer is open, so one entity’s modifications do not appear on another.

injectSelectOption

Inject a CUSTOM option into another select operation by writing into the engine variable INJECT_SELECT_OPTIONS. Used so an archetype or feat can extend a class’ choice list without rewriting the class.
The value is a stringified InjectedSelectOption, shaped as { opId, option } where opId is the target select operation’s id and option is the new CUSTOM option to add.

sendNotification

Surface a Mantine toast on the sheet, for one-time reminders (“you regained Hero Points”, “your familiar has a new ability available”).

defineCastingSource

Declare a casting source so spell-slot operations and the spell UI know it exists. The class (or archetype) that introduces the source emits this; subsequent giveSpellSlot / giveSpell operations reference its name.
The value shape is <name>:::<tradition>:::<type>:::<attribute>. The engine parses these out when computing spell DCs and slot counts.

See also