Skip to content

Spell Notes

Personal, per-user text snippets attached to individual spells. A player keeps house-rule annotations, combat reminders, or flavor text tied to a specific spell across every character they play.

Notes are scoped per user, per spell. Two players viewing the same spell on the same actor each see their own notes.


Each spell row displays a sticky-note icon when the Notes element is enabled in Details Customization. A player clicks the icon to open the Spell Notes dialog for that spell.

The outlined, muted far fa-sticky-note icon means no note is saved for that user, actor, and spell. The solid, highlighted fas fa-sticky-note icon means a note exists.

The dialog is a standard-form ApplicationV2 titled Edit Notes for <spell name>. It positions itself next to the icon that opened it, preferring the right side, falling back to the left, then centering when space is tight.

If the sticky-note icon does not appear, confirm the Notes element is enabled in Details Customization.


Spell Notes dialog

FieldDescription
Notes textareaFree-form text. Auto-sizes between three and eight rows based on existing content length.
Character counterShows characters remaining. It enters a warning state as the note approaches the limit and an error state when the limit is exceeded.
Save buttonSubmits the form. Disabled while the textarea is empty or over limit.
Cancel buttonCloses without saving.

The maximum note length is the world-level setting Maximum Note Length (SPELL_NOTES_LENGTH). The textarea enforces the limit through maxlength, and the Save button is disabled when the counter goes negative. See Installation and Settings.


Notes live in the module’s user-spell-data compendium pack inside a journal entry named User Spell Data. Each user owns a page on that journal, identified by a per-page flag holding their userId.

  • Storage helpers: loadUserSpellData(userId) and saveUserSpellData(userId, spellData) in scripts/data/user-data.mjs.
  • Shape: { [spellUuid]: { notes: "string", actorData: { ... } } }
  • UUIDs are stored with . replaced by ~ because Foundry flag keys treat dots as nested paths.
  • A session-scoped cache avoids re-reading the journal on every lookup. The cache is refreshed only when the same user’s data is saved.
  • Data is versioned (dataVersion = "3.1"). Legacy HTML-table data is migrated on first read.

Because notes live in the compendium pack and are keyed by UUID, they survive actor-item rebuilds and spell book re-opens. Removing and re-adding a spell to an actor does not clear its note.

Saving a note refreshes every matching sticky-note icon in the current DOM, keeping the outlined-versus-solid state and tooltip text in sync without a full re-render.


Notes are scoped per user and per spell only. They are stored as a flat string at spellData[uuid].notes. The actorId in the dialog form is passed to getTargetUserId(actor) to resolve which user’s journal page to write; it does not sub-key the note inside the payload. Actor-id sub-keying inside the payload applies to favorites (spellData[uuid].actorData[actorId].favorited), not notes.

  • When a player opens the dialog, their own userId is used.
  • When a GM opens the dialog on a player-owned actor, the note attaches to that player, not the GM. A GM annotating their own actor uses their own id. A GM does not see a player’s notes unless actively impersonating the owning user.

Two players on the same shared actor each annotate the same spell with different text, and each sees only their own.

Notes are not broadcast. They stay local to the owning user and are never sent over the module’s socket channel, even with shared-with-party features enabled.


Notes can be injected into the spell’s description on the actor sheet so they appear inline whenever the spell tooltip or sheet is viewed. The client-scope setting Inject Notes into Spell Descriptions (SPELL_NOTES_DESC_INJECTION) controls whether notes are injected and whether they are prepended or appended. Each user chooses their own preference. See Installation and Settings.

The DescriptionInjector class in scripts/ui/description-injector.mjs manages all injection. Injected content is wrapped so the injector can find, replace, or remove it with a regex:

<div class='spell-book-personal-notes'><strong>Personal Notes:</strong> ...</div>

Injection runs from these triggers:

  • createItem hook: when a spell is added to an actor, its description is updated if the user has a note for that spell.
  • updateItem hook: when a spell’s description changes, notes are re-applied. Updates marked with spellBookModuleUpdate: true (the injector’s own writes) are ignored to prevent recursion, and a short-lived _updatingSpells Set provides a second recursion guard.
  • Saving a note in the dialog: handleNotesChange(spellUuid) walks every actor and updates every matching spell copy.

Changing the injection setting does not bulk-strip or reapply existing notes. A new setting value takes effect on the next spell add, description change, or note save.

Notes text passes through dnd5e.utils.formatText and foundry.utils.cleanHTML before injection. Simple formatting is preserved, and unsafe HTML is stripped.

Spell UUIDs are canonicalized through getCanonicalSpellUuid before lookup, so compendium copies and actor-owned copies of the same spell resolve to the same note entry.


The sticky-note icon is rendered by buildNotesIcon in scripts/ui/spell-render.mjs. It is emitted only when the notes element is enabled in Details Customization and the spell has a resolvable UUID.

A player who does not want the icon disables the Notes element in Details Customization. That is a client-scope setting, so it affects only that user’s UI.