Handlers

Handlers are build-time transforms that turn a special markdown form into HTML. Two trigger shapes:

  • Inline: `prefix: content`, an inline code span whose content starts with a registered prefix and a colon.
  • Code block: ```lang, a fenced code block whose language tag names a registered handler.

Four handlers are built in, and you can add your own under .vaults/handlers/:

HandlerTriggerDemo
fminlinebelow
fmcode blockbelow
gallerycode blockbelow
downloadcode blockbelow

The TTRPG add-on, @wizzlethorpe/vaults-ttrpg, brings five more. Install it beside the CLI and they work the same way:

HandlerTriggerDemo
diceinlinebelow
statblockcode blockStatblocks
battlemapcode blockBattlemaps
foundry-installcode blockFoundry integration
fvtt-linkinlineFoundry integration

Add-on: `dice:`

Click the rolled die for a fresh result.

MarkdownRenders as
`dice: 1d20+5`
`dice: 8d6`
`dice: 1d100`

An unrecognised formula renders as a struck-through code span:

MarkdownRenders as
`dice: not-a-formula`not-a-formula

Supported syntax: XdY, XdY+Z, XdY-Z. Advantage, exploding and keep-highest notation are not supported.

Built-in: `fm:`

Inserts a value from the page's frontmatter. This page's frontmatter is:

title: Handlers

So `fm: title` renders as: Handlers. Values pass through a small inline formatter: **bold**, *italic* and `code` render; wikilinks do not.

A missing key renders a visible warning marker, so a typo surfaces instead of an empty string:

MarkdownRenders as
`fm: nope`{{nope}}

Date values (YAML parses ISO 8601 dates) format as YYYY-MM-DD. Arrays join with , . Objects emit the warning marker, but a dot path walks into them: `fm: stats.hp` reads a nested key, and a missing segment anywhere along the path triggers the warning.

Numeric segments index into arrays: `fm: foundry.patch.results.0.description` reads the first row's description. Witchwood encounters uses this to render a RollTable defined in foundry.patch as a markdown table in the page body, with nothing duplicated between the Foundry document and the wiki.

For a value that belongs in <pre><code> (a script body, a long string) there is a fenced form keyed on fm. The body is the dot path; text after the language tag is the language hint for the rendered code element:

```fm javascript
foundry.patch.command
```

Renders as <pre><code class="language-javascript">…</code></pre>. The macro pages (Toggle feast, Toggle lights, Toggle ambient noise) display their command source this way.

Add-on: statblock

A code-block handler keyed on ```statblock, schema-compatible with the Fantasy Statblocks Obsidian plugin. See Statblocks for the full demo.

Pseudodragon

Tiny dragon neutral good

Armor Class 13

Hit Points 7 (2d4 + 2)

Speed 15 ft., fly 60 ft.

STR
6 (-2)
DEX
15 (+2)
CON
13 (+1)
INT
10 (+0)
WIS
12 (+1)
CHA
10 (+0)

Skills Perception +5, Stealth +4

Senses blindsight 10 ft., darkvision 60 ft., passive Perception 15

Languages understands Common and Draconic but can't speak

Challenge 1/4

Keen Senses. The pseudodragon has advantage on Wisdom (Perception) checks that rely on sight, hearing, or smell.

Actions

Bite. Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: piercing damage.

The button inside the action description works because handler descriptions run through the inline handlers.

A responsive grid of thumbnails. One image per line, named the way a ![[file]] embed names it, with an optional caption after a pipe. Lines starting with # are comments.

```gallery
screenshot-fvtt-actor-aelar-galanodel.webp | Aelar as a dnd5e Actor
screenshot-fvtt-item-potion-of-healing.webp | The potion as an Item
screenshot-fvtt-journal-bram-mossfoot.webp | Bram's journal entry
```

Images resolve through the same index as ![[ ]] embeds, so anything a gallery names is staged into the deploy and gated per role like any other image.

Built-in: download

A download link for a file in the vault:

```download
file: Mossfoot/Audio/mossfoot-tavern.ogg
label: Mossfoot tavern ambience
note: 1.4 MB, OGG
```

The file ships only to the variants of the pages that reference it, and the auth middleware serves it only to those roles, the same gating as any other passthrough. A download block stages its file whatever the extension, so it also covers files outside the recognised list on Passthrough files.

foundry-install renders a download link for the reader's own grafts.json. fvtt-link: (empty) links to the Foundry document a page builds rather than to its journal page. Both are documented on Foundry integration.

Writing a custom handler

Put an .mjs file in .vaults/handlers/ that exports a handler (or handlers: []):

// .vaults/handlers/shout.mjs
export const handler = {
  inline: "shout",
  render(content, ctx) {
    return { html: "<strong>" + content.toUpperCase() + "</strong>" };
  },
};

Now `shout: hello` renders as a bold uppercase HELLO anywhere in the vault.

The handler API:

  • Inline: { inline: "prefix", render(content, ctx) }
  • Code block: { codeBlock: "lang", render(content, ctx) }
  • Images a block names: a code-block handler may add imagePaths(content), returning the vault-relative paths of images its block refers to, such as attachments/maps/cellar.webp. They ship with the page even when nothing else embeds them, and only to readers who can see the block. A bare filename does not resolve.
  • Return { html: "..." } to insert raw markup, or { markdown: "..." } to run the result through the rest of the pipeline, so wikilinks resolve, embeds inline, and dice buttons in the output are picked up.
  • ctx.frontmatter is the page's parsed frontmatter; ctx.pagePath is the page's basename without its extension; ctx.escape(s) HTML-escapes a string; ctx.applyInlineHandlers(s) runs the other inline handlers over a string, which is how statblock supports inside a desc.

Browser-side assets

A handler can ship JS and CSS with the deploy:

export const handler = {
  codeBlock: "widget",
  assets: {
    scripts: ["./widget.runtime.js"],
    styles: ["./widget.css"],
  },
  render() { return { html: '<div class="widget"></div>' }; },
};

Paths resolve relative to the handler file and must stay inside .vaults/handlers/; a path outside it fails the build. The assets bundle into _handlers.js and _handlers.css, served to the wiki only. In Foundry the HTML a handler produced survives, but its styling and behaviour do not.

_handlers.css loads after the theme and before your CSS snippets, so a snippet can restyle a handler.

Clicking an image in a page opens it in a lightbox. Give an element the class lightbox-layers and the images inside it open together, stacked at the first image's size, which is how a battlemap level opens as one picture.

This vault includes a `clicker:` inline handler with a script and a stylesheet: `clicker: try me` renders as .

Created · Updated