Documentation Index
Fetch the complete documentation index at: https://docs.wanderersguide.app/llms.txt
Use this file to discover all available pages before exploring further.
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: string, // UUID, unique per operation row (use crypto.randomUUID())
type: OperationType, // see the catalog below
data: { ... } // shape varies by type
}
id is required by the engine for deduplication and result tracking. Never reuse one across operations.
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:
| Type | Examples |
|---|
num | MAX_HEALTH, SPEED, FOCUS_POINTS |
str | SIZE, CASTING_SOURCES |
bool | USE_PIERCING_DAMAGE_RESISTANCES, DUAL_CLASS |
prof | SKILL_ATHLETICS, SAVE_FORT, WEAPON_SIMPLE |
attr | ATTRIBUTE_STR, ATTRIBUTE_DEX |
list-str | RESISTANCES, WEAKNESSES, IMMUNITIES, LANGUAGES, SENSES |
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).
{
"id": "<uuid>",
"type": "createValue",
"data": {
"variable": "MY_CUSTOM_COUNTER",
"type": "num", // 'num' | 'str' | 'bool' | 'prof' | 'attr' | 'list-str'
"value": 0,
},
}
If the variable already exists, this is a no-op.
setValue
Hard-set a variable. Wins over any prior value, including setValues from other content (last-write-wins).
{
"id": "<uuid>",
"type": "setValue",
"data": { "variable": "SIZE", "value": "LARGE" },
}
Use sparingly: it stomps any other contribution. Prefer adjValue or addBonusToValue when stacking matters.
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.
// Numeric
{ "id": "...", "type": "adjValue", "data": { "variable": "MAX_HEALTH", "value": 5 } }
// Proficiency step up
{ "id": "...", "type": "adjValue", "data": { "variable": "SKILL_ATHLETICS", "value": "1" } }
// Resistance / weakness / immunity
{ "id": "...", "type": "adjValue", "data": { "variable": "RESISTANCES", "value": "fire, 5" } }
{ "id": "...", "type": "adjValue", "data": { "variable": "IMMUNITIES", "value": "poison" } }
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).
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:
value | type | text | Renders as |
|---|
1 | "item" | "" | +1 item bonus |
1 | "circumstance" | "to Climb trees" | +1 circumstance bonus to Climb trees |
null | "" | "If you get a success, you get a critical success instead." | If you get a success, you get a critical success instead. |
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
// Plain typed bonus
{
"id": "<uuid>",
"type": "addBonusToValue",
"data": { "variable": "AC_BONUS", "value": 1, "type": "item", "text": "" }
}
// Conditional numeric bonus: value still renders, text qualifies it
{
"id": "<uuid>",
"type": "addBonusToValue",
"data": { "variable": "SKILL_ATHLETICS", "value": 1, "type": "circumstance", "text": "to Climb trees" }
}
// Pure mechanical note shown under the stat, no number
{
"id": "<uuid>",
"type": "addBonusToValue",
"data": {
"variable": "SAVE_FORT",
"value": null,
"type": "",
"text": "If you get a success, you get a critical success instead."
}
}
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.
{
"id": "<uuid>",
"type": "bindValue",
"data": {
"variable": "CLASS_DC",
"value": { "storeId": "CHARACTER", "variable": "ATTRIBUTE_STR" },
},
}
storeId is 'CHARACTER' for the active character; companion stores use their own IDs.
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.
{
"id": "<uuid>",
"type": "giveAbilityBlock",
"data": { "type": "feat", "abilityBlockId": 20015 }
}
{
"id": "<uuid>",
"type": "removeAbilityBlock",
"data": { "type": "feat", "abilityBlockId": 20015 }
}
type is one of: 'action' | 'feat' | 'physical-feature' | 'sense' | 'class-feature' | 'heritage' | 'mode'.
giveLanguage / removeLanguage
{ "id": "...", "type": "giveLanguage", "data": { "languageId": 42 } }
{ "id": "...", "type": "removeLanguage", "data": { "languageId": 42 } }
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).
{
"id": "<uuid>",
"type": "giveSpell",
"data": {
"spellId": 4623,
"type": "INNATE", // 'NORMAL' | 'FOCUS' | 'INNATE'
"castingSource": "wizard", // optional, defaults vary
"tradition": "ARCANE", // optional
"rank": 3, // optional, locks the rank
"casts": 1 // optional, number of innate casts/day
}
}
{ "id": "...", "type": "removeSpell", "data": { "spellId": 4623 } }
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).
{
"id": "<uuid>",
"type": "giveSpellSlot",
"data": {
"castingSource": "wizard",
"slots": [
{ "lvl": 1, "rank": 1, "amt": 2 },
{ "lvl": 3, "rank": 2, "amt": 2 },
],
},
}
giveItem
Add an item to the character’s starting inventory.
{ "id": "...", "type": "giveItem", "data": { "itemId": 4002 } }
giveTrait
Add a trait to the character itself. Rare; used when an ancestry or archetype literally makes you have a trait (e.g. humanoid).
{ "id": "...", "type": "giveTrait", "data": { "traitId": 1542 } }
Branching and choices
conditional
Run nested operations only when conditions match. Conditions check variable state.
{
"id": "<uuid>",
"type": "conditional",
"data": {
"conditions": [
{
"id": "<uuid>",
"name": "When trained in Athletics",
"type": "prof",
"operator": "GREATER_THAN_OR_EQUALS",
"value": "T",
},
],
"trueOperations": [{ "id": "...", "type": "adjValue", "data": { "variable": "SKILL_ATHLETICS", "value": "1" } }],
"falseOperations": [],
},
}
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.
select
Player-facing choice. Two modes:
Predefined: you list the options inline.
{
"id": "<uuid>",
"type": "select",
"data": {
"title": "Choose a benefit",
"description": "Pick one of the following.",
"modeType": "PREDEFINED",
"optionType": "CUSTOM",
"optionsPredefined": [
{
"id": "<uuid>",
"type": "CUSTOM",
"title": "Tough",
"description": "+1 HP per level.",
"operations": [{ "id": "...", "type": "adjValue", "data": { "variable": "MAX_HEALTH_BONUS_PER_LEVEL", "value": 1 } }],
},
{
"id": "<uuid>",
"type": "CUSTOM",
"title": "Quick",
"description": "+5 ft Speed.",
"operations": [{ "id": "...", "type": "adjValue", "data": { "variable": "SPEED", "value": 5 } }],
},
],
},
}
Filtered: you describe a content query and the engine renders all matches.
{
"id": "<uuid>",
"type": "select",
"data": {
"title": "Pick a 1st-level wizard feat",
"modeType": "FILTERED",
"optionType": "ABILITY_BLOCK",
"optionsFilters": {
"id": "<uuid>",
"type": "ABILITY_BLOCK",
"level": { "min": 1, "max": 1 },
"abilityBlockType": "feat",
"traits": ["wizard"],
},
},
}
optionType and the matching filter type must agree. Valid pairs:
optionType | Filter type | What the player picks |
|---|
CUSTOM | (none, predefined only) | One of your inline operations blocks |
ABILITY_BLOCK | ABILITY_BLOCK | A feat / action / etc. |
SPELL | SPELL | A spell (for grant) |
LANGUAGE | LANGUAGE | A language |
TRAIT | TRAIT | A trait |
ADJ_VALUE | ADJ_VALUE | A skill / weapon group / armor group to bump |
The chosen option id is recorded on character.operation_data.selections, keyed by the select operation’s id.
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.
{
"id": "<uuid>",
"type": "injectText",
"data": {
"type": "feat",
"id": 20015,
"text": "If you took the Healer dedication, you can also use this with Medicine.",
},
}
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.
{
"id": "<uuid>",
"type": "injectSelectOption",
"data": {
"variable": "INJECT_SELECT_OPTIONS",
"value": "{ ...InjectedSelectOption JSON... }",
},
}
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”).
{
"id": "<uuid>",
"type": "sendNotification",
"data": {
"title": "Hero Points",
"message": "You start each session with 1 Hero Point.",
"color": "blue",
},
}
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.
{
"id": "<uuid>",
"type": "defineCastingSource",
"data": {
"variable": "CASTING_SOURCES",
"value": "wizard:::ARCANE:::PREPARED:::INT",
},
}
The value shape is <name>:::<tradition>:::<type>:::<attribute>. The engine parses these out when computing spell DCs and slot counts.
See also