The data model
WG content lives in Postgres, with one table per primary content type:
A second layer, the frontend cache, mirrors a subset of these tables in the browser so the character sheet doesn’t hit the database for every lookup. The cache is populated at app start from
find-* calls and refreshed when the active character’s content_sources.enabled list changes.
Weapon hands is a top-level field holding the printed value, such as 1, 2, or
1+. usage is separate: leave it empty when the source lists Hands without a Usage
line. Magical equipment can have a real Usage line, which should be preserved.
Weapon reload is stored in meta_data.reload; the thrown-weapon placeholder is —.
Spellhearts may store explicit printed casting values in optional
meta_data.spellheart_casting. Its attack and dc fields are finite integers;
at least one is required. Missing fields are unknown and are never inferred from
the other value. Spellheart cantrips can use a higher corresponding value from
the character’s current spellcasting sources. Other spellheart spells use the
explicit item values. This metadata does not change staff or wand casting, spell
frequency, or saved item snapshots.
Older saved Spellhearts without explicit casting values can use the printed values
from their matching, enabled official catalog item. Saved overrides take precedence;
the saved description, operations, and item settings remain unchanged.
The confirmed historical Wand of the Ash Puppet spell reference can be read for spell
indexing without replacing its saved description or initializing charges. This does
not add an activation or switch the saved item to a different printing.
Wand and staff casts use one current spellcasting source whose tradition lists the
spell, or whose normal spell list includes that spell. Multiple eligible sources use
the highest spell DC, then attack modifier. Attack and DC stay paired with that source.
This calculation does not grant casting permission or change saved items, staff
preparation, charges, or spell slots. Characters without a matching source keep the
existing manual fallback; Trick Magic Item checks are not automated.
Spellheart spell lists read the item’s casting activations. Plain You cast and
You [cast](link_action_19611) expose the same spells and printed ranks. Passive
spell references, prerequisites, literal code examples and other linked actions
do not grant additional cast spells. Comma-separated casting alternatives retain
each printed rank. Headerless legacy descriptions retain first-link compatibility.
Automatic Bonus Progression’s level-17 Ability Apex increases the selected attribute
modifier by 1 or raises it to +4, whichever is higher. This is a full increase even
above +4, and preserves any existing half-boost. Ordinary attribute boosts retain
their usual half-boost behavior above +4.
Fixed attribute boosts inside a selected custom ancestry option constrain free boosts
in the same ancestry. Switching options ignores saved free boosts that now conflict.
Alternate ancestry boosts replace nested attribute adjustments while retaining size
and other non-attribute effects.
The legacy Heal Companion feat grants the current Heal Companion focus spell through
the Ranger casting source, with Wisdom, trained spell proficiency, and the Spells tab.
It is deprecated and hidden from new feat choices. Its original feat ID and grants
remain valid for saved characters that already selected it.
Class archetype features can be embedded in feature_adjustments instead of stored
as separate ability-block rows. The sheet and exports resolve these records using the
calculated class feature IDs, including replacement features and either dual class.
Conditional choices within selected feats remain visible alongside ordinary grants.
This includes dedication skill choices and feat choices in Free Archetype slots.
Standalone list expressions such as {{FEAT_NAMES}} display their entries. Use
INCLUDES when testing list membership inside an arithmetic expression.
Characters with multiple casting sources can choose Cast staff using in the staff’s
spell list. Adding charges uses a slot from the selected prepared source; spontaneous
casting consumes a slot from the selected spontaneous source.
Deleting a character note page applies pending text edits before removing the page.
The remaining editors reload their own contents, and deleting the last page leaves
one blank Notes page. Editing or removing an imported creature ability affects only
the selected row, even when legacy rows share an ID.
Character calculation failures stay in background diagnostics. The sheet and builder
remain editable, and retain the last successful calculation during the current session.
A transient failure gets one automatic retry. Persistent failures wait for changed
inputs; raw edits remain buffered locally, and incomplete derived stats never save.
Predefined ability, spell, and language choices in the operation editor have an X
to remove that choice. Other choices retain their existing IDs and values, including
when the removed choice was first in the list. Clearing the only choice leaves an
empty picker ready for a replacement.
Hazards use the creature table for source ownership and search, but they are not living entities. Creature queries default to type='creature'; hazard queries use type='hazard'. A hazard’s stat block lives in structured details fields for stealth, disable, defenses, activation, routine, and reset. Official hazard corrections can be submitted from the hazard drawer for moderator review as content updates. They use a hazard-specific editor and remain separate from the creature editor.
Encounters offer separate Add Creature and Add Hazard selections. A hazard
combatant stores type='HAZARD' and a hazard stat-block snapshot alongside optional
hazard_state.hp_current and hazard_state.disabled values. Each instance has its
own _id, so damage or disabling one instance never edits the catalog or another
instance. HP controls appear only when HP is listed. Disabling is manual; reaching
zero HP or a Broken Threshold does not automatically resolve a hazard’s special rules.
Complex hazards roll their explicitly listed Stealth modifier for initiative; simple
hazards do not roll initiative. Existing assigned initiative is skipped unless selected
again. Simple hazards contribute one fifth of creature XP at the same level, while
complex hazards contribute full XP. Neither counts toward party size or party level.
Hazards do not use creature adjustments, condition controls, or character calculations.
The character sheet’s Extras tab is unchanged.
Hazard prose links each named spell, action, energy trait, and source creature
reference. Creature references use the existing read-only stat-block view and
drawer history; returning to the hazard preserves the encounter instance.
Creature rows can carry optional meta_data.stat_block annotations for source-specific stat lines, including listed senses and skills, trait labels, conditional defense and HP notes, and innate spell frequencies. immunities_note and resistances_note display conditional effects in their stat-block lists without treating them as unconditional mechanics. These annotations only affect display. Embedded attack items can use meta_data.display_traits for trait text that has no matching trait record and meta_data.inventory_label when an inventory label differs from the attack name. The underlying creature operations and abilities still hold the actual mechanics.
Reviewing content submissions
Content update requests show the content type, applicable hidden setting, repeatability for ability blocks, and classification switches for traits. Updates compare the original and submitted settings. Expand Full content data to inspect the complete read-only records, including operations and metadata omitted from the rules preview. The original and updated rules previews remain available alongside these review details. Reviewing a request does not edit or approve it; moderation still happens in Discord.Loading and saving safely
A content package becomes available only after every requested table and source lookup succeeds. A successful empty table is valid; a failed request remains an error. The sheet and builder offer Retry and do not calculate or save against a partial package. Browser caches follow the signed-in account and resolved source set, and resets discard late requests and hydration from the previous cache generation. Content cache cleanup preserves unsynced character drafts. The character list requires the same signed-in account that requested it. If its authentication session becomes unavailable or changes, the list shows a load error with Retry instead of treating an anonymous response as an empty account. Optional browser-cache work cannot block authoritative content loading. Homebrew invalidation clears the current in-memory generation immediately and queues persistent deletion in order with other storage writes. Reading storage has a 2.5-second deadline; a late read cannot publish rows after fresh downloads start. A recent snapshot gets a separate 750-millisecond source-version check. Confirmed changes reject the snapshot; a slow or unavailable check uses the account-scoped snapshot for this visit. A late verdict never replaces content during calculations. Unverified snapshots keep their original timestamp when persisted, preserving the 24-hour age limit. If browser storage itself remains stalled, fresh content still loads but persistence waits for that storage operation to finish. Catalog artwork loads only when displayed. Calculation workers reuse the package posted for their current job. Catalog selections remain scoped to its enabled sources, and the reader is released after success or failure. A missing required table fails the calculation. Explicit grants can reference content from another book; when that reference is absent from the package, the existing scoped lookup still runs without storing fallback rows in the worker cache. A failed lookup stops calculation rather than dropping the grant. Such uncached dependencies still require a connection. Explicit main-thread calculations retain the normal content store behavior. Trait-name filters also receive a separately cached INFO+PAGE trait catalog, preserving the endpoint’s first-by-ID choice when names occur in more than one book. This optional metadata starts loading with the PAGE package and gets at most 750 milliseconds of extra wait after the required tables finish. Failure or a late result cannot block an unrelated character or mutate a posted package; an actual missing lookup uses the existing scoped request. Spell panels use the same complete page package, so an early render cannot cache an empty catalog while a character is being restored. The builder waits for the current character’s authoritative save context before exposing editable controls. A cached route row cannot make the form ready early. A failed load leaves the character route available for retry. Save status distinguishes server confirmation from a locally retained edit; reconnect and retry use one serialized save queue. Each browser page keeps its own recovery copy, so concurrent tabs cannot silently overwrite another tab’s local draft. Open character pages check for remote edits every five seconds while visible and on reconnect or focus. Incoming edits advance the saved baseline before changing the editor, so polling cannot echo the same character back through autosave. A read started before a newer save, account change or route change cannot replace the current state. Failed reads leave the loaded sheet available. Remote edits use the same merge and conflict handling as save recovery. Incoming conditions and selections invalidate older calculations. If the reconciled character needs a different source set, the page loads that exact package before calculating or saving. This includes unsynced source choices restored from a draft; repeatedly loading the server’s older sources would otherwise create a reload loop. Concurrent character edits merge at nested fields and stable entry IDs. Independent changes survive. When both writers change the same value, saving pauses quietly. Background reads continue, and ordinary edits recheck the latest competing version. Saving resumes automatically once the versions merge safely. While a conflict remains, the editor retains its local input and original base instead of treating unrelated remote fields as local changes. Both survive navigation and reload when draft storage is available; the competing server version is not overwritten. Recovery never displays toasts, reload prompts, or conflict-choice buttons. Session expiry, revoked write access, rejected saves, and unavailable draft storage also remain quiet. Pending input is retained in the editor; available local storage preserves its account-scoped draft. Calculation completion applies every guarded state update; it does not debounce several updates into only the last one. Health normalization reads the latest HP when applying the result, so a slow calculation cannot replace newer damage or healing with an older clamped value. Input debouncing remains in place before running operations. HP initialization clears its reset flag even if the numeric HP already equals the maximum, so a later condition recalculation cannot heal newly received damage. Explicit condition edits apply their HP consequences in the same saved update on the sheet, companions, and campaign encounter controls. Increasing Drained loses current HP equal to the increase times the character’s level (minimum level 1), as well as reducing maximum HP. Decreasing or removing it does not restore current HP. Loading an existing Drained condition, recalculating, retrying a save, or receiving a remote update never deducts HP again. This HP loss is not damage and does not add Dying. Removing Dying increases Wounded once. Stabilizing at zero HP leaves the character Unconscious; subsequently healing that stable character does not increase Wounded again. Normalization and loading do not create these combat events. Drained with its HP and level, and Dying/Wounded with their HP transition, are coupled edits. A concurrent change to these values pauses saving even if two independent actions happened to produce the same HP number. The local recovery copy preserves the complete local transition for review. Identical accepted snapshots from a lost save acknowledgement reconcile without replaying the action. Condition dialogs apply their edit to the current entity when selected, including remote HP received while the dialog was open. Companion and GM controls preserve that current state through the same update, including queued unsynced GM damage.Public vs. homebrew
find-* endpoints implicitly filter to official, published sources unless you pass content_sources (or query by a specific id). This is the rule baked into fetchData(). Without it, a search would surface every random homebrew row in the database.
Every entity that lives in a content source carries a generated uuid (a hash of name + type + level/rank + content_source_id) so duplicate entries within the same source are caught at insert time.
Character JSON exports
Version 4 JSON exports retain the saved character incharacter and its calculated
data in content. content.companions contains each companion’s calculated data
in the same format, including attributes, proficiencies, maximum HP, AC, spells,
slots, named languages and senses, defenses, speeds, abilities, and traits.
Each entry has index, id, name, and content. Its index points to
character.companions.list[index], including when two companions share a catalog ID.
Companions use the character’s enabled sources and current calculated owner bindings.
The saved character and companion records stay unchanged; import still reads character.
Operations
Speed assignments establish a base speed; when several sources grant one, the highest base applies. Speed adjustments add to or subtract from that base, regardless of which source runs first. For example, a Human’s 25-foot base speed and a custom +5 adjustment produce 30 feet in both the builder and character sheet. Removing a grant recalculates the remaining base and adjustments. Mode keys retain numbers:Cursebound 2 and Cursebound 3 toggle independently, and
ACTIVE_MODES conditions distinguish them. Existing shared keys from numbered modes
retain the effects that were active before the update until those modes are toggled
individually.
Companion bindValue operations keep their saved source store and variable visible
when reopened, including custom destination values created only in the companion.
Companion conditions can read bindings from the already calculated parent character.
Binding to the character’s FEAT_NAMES lets companion conditionals inspect those feats.
Known spells use the saved ranks for their own casting source. A spell learned from
two sources appears once per rank in each source; distinct heightened versions remain
available within that source.
The operations column on each content row is what makes WG content actually do something to a character. See Operations for the full catalog of types and their JSON shapes.
When you POST a content row to a create-* endpoint, you’re sending operations in raw JSON. The API treats them as opaque (the OpenAPI schema uses additionalProperties: true); validation happens at character-build time on the frontend.
Eidolon attacks and equipment
Open a companion’s drawer and choose Configure attacks to reach its Inventory, where you can add and configure its natural attacks. Give those attacks the Unarmed Attack category so the sheet treats them as unarmed strikes. For a summoner or a character with an eidolon, Inventory offers Invest on magic weapons. Invest and equip one weapon to share its potency and striking runes with the eidolon’s unarmed attacks. A weapon with its own Invested trait keeps its ordinary investment; use Share runes to choose it as the eidolon’s source. Changing the shared weapon preserves those ordinary investments. A weapon inside a container is stowed and does not confer its runes. Invested Handwraps of Mighty Blows (including Dragon Handwraps) provide the fallback when no selected invested weapon is held. The optionalinventory.eidolon_weapon_id records the shared
weapon’s inventory-entry ID. Divesting or deleting that entry clears the selection.
These bonuses are calculated from the current inventory without rewriting the companion’s
saved attack. This automation covers fundamental weapon potency and striking runes.
Property rune compatibility and other equipment benefits still require manual configuration.
Linking content from prose
Content references are Markdown links with a drawer target:id, not its uuid. Ability-block links
use the appropriate subtype, such as action, feat, or class-feature, even
though those records share the ability_block table.
Generate links with
convertToHardcodedLink(type, name, displayText?).
Do not type or guess target IDs. Resolve the intended official printing first,
using source-scoped content lookups rather than homebrew or playtest name matches.
When names collide, verify the target’s type, source, level or rank, and rules.
For example, these calls produce the separate references in a magical weapon’s
opening phrase. Rune records use the item link type:
What to link in prose
Link every relevant occurrence, not only the first mention. This includes descriptions, heightened entries, and prose in requirements, targets, triggers, costs, frequency, access, and special clauses. A source website’s existing links are a starting point, not a complete WG indexing checklist.
Disambiguate by context. A named spell reference is not a same-named trait, and
an ordinary action verb is not necessarily a named rules action. Do not run a
global word replacement over all fields.
Condition links are added after Markdown parsing, leaving authored links and code
untouched. Persistent damage also auto-links across emphasis and linked damage-type
lists. The condition and traits stay separately clickable without nested links or
changed wording. The condition blacklist also applies to persistent damage.
Preserve formatting and field meaning
- Items, traits, and class-feature references use lowercase prose labels. Spell references use lowercase italic labels. Feats and named actions use Title Case. Preserve the source’s emphasis for magic items and self-references.
- Keep existing valid links, code spans, and code blocks intact. Do not re-link words inside an authored link or convert literal examples into game references.
- Preserve the book’s divider between item stat lines and description text. Do not add dividers absent from the source or leave empty trailing dividers.
- Preserve Markdown hard line breaks. Consecutive stat lines need two trailing spaces when a line break, rather than a new paragraph, is intended. A bare newline may join those lines in the renderer.
- Add action-symbol markup only for an explicit printed action cost, not each
prose mention of Interact, Strike, or another action. For example,
<abbr cost="ONE-ACTION" class="action-symbol">1</abbr>represents a printed one-action cost; the named action remains a separate content link. - Keep activation labels, requirements, and frequency distinct. Do not move a requirement into frequency merely because another website labels it differently.
- Do not add new headings, bullets, citation notes, or explanatory wording to printed rules merely to describe a repair. Use clean punctuation in new prose.
- Prerequisite arrays, trait ID arrays, operations, and selection filters are structured engine inputs, not Markdown fields. Do not insert prose links into them during a formatting pass.
Source verification and book completion
Compare each record’s complete rules and stat fields with the correct final printing, not just its name. Preserve verified book, page, and Archives of Nethys URL metadata undermeta_data.source.book, meta_data.source.page, and
meta_data.source.url. Store the exact entry URL, not a search page or an
unrelated similarly named entry. Citation metadata is separate from player-facing
rules text; a formatting-only edit preserves it.
When sources disagree, record the exact discrepancy privately and compare the
official rules or errata and other curated implementations. Do not silently
invent omitted values, copy generated tables without checking their rules, or
assume every Foundry record reflects the latest printing.
A book inventory is a finite checklist, not a correctness certificate. Separate
printed entries from WG scaffolding, generated grade variants, navigation pages,
and unsupported rule systems. Existing source-owned records and dependencies in
other books still need verification. A matching name or a successful schema parse
does not prove the content is complete or mechanically connected.
Verification checklist
- Reconcile every applicable printed entry, its fields, reprints, and dependencies. Keep source discrepancies and unsupported mechanics explicit.
- Check every prose field for all relevant reference occurrences and verify each target’s actual identity. Preserve operations and structured prerequisites when the change is formatting-only.
- Validate complete proposed rows with the app’s schemas. See Validating content for the existing commands.
- Test the actual character or encounter controller for mechanical changes, including removal, reload, source choices, stacking, and unchanged saved input. Do not model temporary activation effects as unconditional passive bonuses.
- Inspect the actual drawer and casting view on desktop and mobile. Check headers as well as the description: valid Markdown in storage is not enough if the consuming field renders plain text. Count repeated links, open their targets, return through drawer history, and check condition links and line breaks.
- Before a write, check pending curator submissions and protect the exact current values from concurrent changes. Afterward, read back the complete saved row, validate it, and verify the intended rendered result. Preserve existing saved characters and playtest identities.
- Report imported content, privately staged content, verified behavior, and unresolved work separately. Keep private audit reports and unpublished records out of Git. Do not call an incomplete audit a completed book.
See also
find-content-source: list books, including homebrew you own.create-content-source: start a new homebrew book (Patreon tier 2+).create-content-update: submit an edit or erratum for community review.search-data: full-text search across every content type.
Homebrew editor and ancestry behavior
Reopening a cached content editor or creature preview initializes its saved fields again. Editing or deleting a creature’s base ability targets that entry’s position, including older creatures whose base abilities share the placeholder ID-1.
An ancestry can grant another ancestry trait with the same name as its base
ancestry. Both trait IDs remain available when filtering ancestry feats and
heritages. Copied class features recognize feats carrying a class trait even when
the homebrew class uses a different trait, so the class feat picker retains its
archetype and dedication tabs.
Numeric conditions use a variable’s calculated value, including typed bonuses
from active modes. This lets a mode raise a counter such as CURSEBOUND and have
dependent conditions match the value shown by inline expressions. Companion
calculations refresh when their owner’s calculated variables change.
