Skip to main content

Stat Test Page

D20 Hiraeth Archives & Guides

BookStack D&D 5e Statblock Guide

This guide documents the JSON and Tetra-cube .monster workflow for the BookStack statblock renderer. Install the renderer once in BookStack custom head content, then paste small HTML wrappers into any page. The wrapper contains JSON; the renderer turns that JSON into a styled D&D 5e monster statblock when the page loads.

Files

Main Renderer

bookstack-custom-html-with-json-statblocks.html

Contains the CSS and JavaScript that should live in BookStack custom HTML head content.

Paste Helper

bookstack-monster-paste-helper.html

Local helper page for loading, dropping, or pasting a .monster file and generating the wrapper HTML.

This Guide

bookstack-dnd-statblock-guide.html

Human-readable usage reference with examples you can copy into BookStack source mode.

Quick Start

This setup has two different pieces. The CSS and JavaScript are installed globally in BookStack once. The monster data is pasted on individual BookStack pages whenever you want a statblock.

  1. Open bookstack-custom-html-with-json-statblocks.html.
  2. Copy the statblock CSS and script into BookStack's Custom HTML Head Content area.
  3. Open a BookStack page in source or HTML mode.
  4. Paste a wrapper such as <div class="dnd-monster-data">...</div> with JSON inside it. Use dnd-monster-data for Tetra-cube .monster files and dnd-statblock-data for hand-authored JSON.
  5. Save the page. The renderer hides the wrapper and replaces it with a styled D&D 5e statblock.
Recommended: Use <template class="dnd-monster-data"> when BookStack allows it. Template content is naturally hidden before the renderer runs. If BookStack strips template, use the plain div wrapper instead.

How It Works in BookStack

The renderer is intentionally simple: it does not need a database table, attachment lookup, iframe, remote API, or plugin. It only needs hidden JSON on the page. This makes it portable across pages and keeps the statblock data close to the lore, encounter note, or adventure text that uses it.

Install Once

Put the CSS and JavaScript from the main renderer file into BookStack custom head content. After that, every page can use statblocks.

Paste Per Page

Each monster lives in a wrapper element on the BookStack page. The wrapper is usually a template or div with a class and JSON inside.

Render on View

When someone views the page, the script finds the wrappers, parses the JSON, and replaces each wrapper with the finished statblock.

Fail Clearly

If JSON is broken, the page shows a visible error box with the parse message, line, column, and a short excerpt near the problem.

Where Each Piece Goes

Thing Where It Goes How Often
Statblock CSS BookStack Custom HTML Head Content Once per BookStack instance or theme setup.
Statblock JavaScript BookStack Custom HTML Head Content, after the CSS Once per BookStack instance or theme setup.
.monster JSON Inside a page wrapper such as <template class="dnd-monster-data"> Every time you want to add a monster to a page.
Display options Attributes on the wrapper, such as data-layout="two-column" Only when a statblock needs a different layout or helper behavior.
Editing rule: edit the monster by editing the JSON inside the wrapper. Do not edit the rendered statblock HTML directly, because the script regenerates that HTML from the JSON on page load.

Rendered Statblock Examples

These are actual rendered statblocks, not code samples. They show the visual output you should expect after BookStack replaces the JSON wrapper on a page.

Wrappers

The renderer scans the page for these wrapper classes:

Wrapper Use For
dnd-monster-data Tetra-cube .monster JSON. This is the best class when pasting a saved file from the Tetra-cube statblock generator.
dnd-statblock-data Hand-authored normalized JSON using simple fields such as name, armorClass, abilities, and sections.

Plain div wrapper

Most compatible
<div class="dnd-monster-data">
{
  "...": "paste the full .monster JSON here"
}
</div>

template wrapper

Cleaner before render
<template class="dnd-monster-data">
{
  "...": "paste the full .monster JSON here"
}
</template>

Display Options

Add these attributes to the wrapper. They apply to every monster inside that wrapper.

Attribute Values Effect
data-layout one-column, two-column, wide, full Overrides the width or column behavior. Use one-column for narrow pages and two-column for large monsters.
data-compact true Slightly tightens the block for long monsters.
data-copy-buttons true Adds small copy buttons beside detected attack bonuses, save DCs, and dice formulas.
data-copy true Short alias for data-copy-buttons="true".
data-show-source true Adds a collapsed source/debug disclosure below the rendered block.
data-source true Short alias for data-show-source="true".
data-source-label Any text Changes the label on the source/debug disclosure.

Option Example

<template
  class="dnd-monster-data"
  data-layout="one-column"
  data-compact="true"
  data-copy-buttons="true"
  data-show-source="true"
  data-source-label="Original .monster JSON">
{
  "...": "paste the full .monster JSON here"
}
</template>

Copy/Paste Recipes

Use these when you know the behavior you want but do not want to rebuild the wrapper by hand. Replace the placeholder JSON with either the full contents of a .monster file or a hand-authored statblock object.

Most Compatible .monster Paste

This works well if BookStack is picky about allowed HTML. It uses a normal div, no optional controls, and no source disclosure.

<div class="dnd-monster-data">
{
  "...": "paste the full .monster JSON here"
}
</div>

Clean Editing Wrapper

This is the preferred wrapper when BookStack keeps template tags. The raw JSON stays hidden before the renderer runs.

<template class="dnd-monster-data">
{
  "...": "paste the full .monster JSON here"
}
</template>

Boss Monster with Copy Buttons

Use this for monsters where you will be copying attack bonuses, save DCs, or dice formulas during play.

<template
  class="dnd-monster-data"
  data-layout="two-column"
  data-copy-buttons="true">
{
  "...": "paste the full .monster JSON here"
}
</template>

Debug While Building

This keeps the original JSON attached below the statblock in a collapsed disclosure. It is useful while testing a new monster or diagnosing token output.

<template
  class="dnd-monster-data"
  data-copy-buttons="true"
  data-show-source="true"
  data-source-label="Original .monster JSON">
{
  "...": "paste the full .monster JSON here"
}
</template>

Narrow Page or Sidebar-Friendly Monster

Use one column if a BookStack page has a narrow content area or the monster has long actions that read poorly in columns.

<template
  class="dnd-monster-data"
  data-layout="one-column"
  data-compact="true">
{
  "...": "paste the full .monster JSON here"
}
</template>

Hand-authored JSON Starter

Use this when you are writing a creature directly in BookStack instead of exporting from Tetra-cube.

<div class="dnd-statblock-data">
{
  "name": "Creature Name",
  "meta": "Medium humanoid, neutral",
  "armorClass": "13",
  "hitPoints": "27 (5d8 + 5)",
  "speed": "30 ft.",
  "abilities": {
    "STR": "12 (+1)",
    "DEX": "14 (+2)",
    "CON": "12 (+1)",
    "INT": "10 (+0)",
    "WIS": "11 (+0)",
    "CHA": "13 (+1)"
  },
  "properties": [
    { "label": "Skills", "value": "Perception +2" },
    { "label": "Senses", "value": "passive Perception 12" },
    { "label": "Languages", "value": "Common" },
    { "label": "Challenge", "value": "1 (200 XP)" }
  ],
  "sections": [
    {
      "title": "Actions",
      "features": [
        {
          "name": "Shortsword",
          "text": "Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 5 (1d6 + 2) piercing damage."
        }
      ]
    }
  ]
}
</div>

Tetra-cube .monster Files

The renderer understands Tetra-cube saved statblocks. A .monster file is JSON, so you can open it in a text editor and paste the entire contents into a wrapper. This is the easiest workflow when you already use Tetra-cube to create or edit monsters.

The main advantage of this path is that you do not have to translate the monster into a new schema. The renderer reads the Tetra-cube fields, computes the familiar derived values, resolves supported bracket tokens, and maps the saved sections into BookStack-friendly HTML.

  1. In Tetra-cube, use Save Statblock to download the .monster file.
  2. Open the file as text.
  3. Copy everything in the file.
  4. Paste it into <div class="dnd-monster-data"> or <template class="dnd-monster-data">.
The renderer does not fetch an attached .monster file by URL. Paste the JSON inline. That avoids BookStack attachment permissions, CORS, and login/session issues.

What the Renderer Reads from Tetra-cube

The converter looks for the normal Tetra-cube fields such as size, type, alignment, armor, hit points, speed, ability scores, saves, skills, damage types, senses, languages, CR, traits, actions, bonus actions, reactions, legendary actions, mythic actions, lair actions, and regional effects.

If the Tetra-cube file has doubleColumns enabled, the renderer will default the monster to a wider two-column layout. You can override that with data-layout="one-column" or force a two-column layout with data-layout="two-column".

What Not to Paste

  • Do not paste the visible statblock text from the Tetra-cube web page. Use the saved .monster JSON instead.
  • Do not paste the file path to a .monster file. BookStack viewers will not be able to read a local path from your computer.
  • Do not wrap the data in a <script> tag. BookStack may strip it, and this renderer intentionally uses safer page-content wrappers.
  • Do not edit the rendered output after saving. Edit the JSON inside the wrapper, then let the renderer rebuild the display.

Using the Paste Helper

  1. Open bookstack-monster-paste-helper.html locally.
  2. Drop a .monster file, choose it with the file picker, or paste the JSON into the raw JSON box.
  3. Select wrapper and display options.
  4. Click Use Pasted JSON if you pasted manually.
  5. Copy the generated HTML and paste it into BookStack source mode.

Hand-authored JSON Model

Use dnd-statblock-data when you want a compact JSON object instead of a Tetra-cube export. This is good for one-off NPCs, quick encounter variants, non-standard monsters, hazard-like creatures, or pages where you want to keep the data short and readable.

The hand-authored model is already close to the rendered output. You provide the text you want to see: armor class, hit points, speed, abilities, property rows, features, and sections. The renderer handles styling, layout, mobile behavior, dark mode, and optional copy buttons.

Rule of thumb: use dnd-monster-data for exported Tetra-cube files. Use dnd-statblock-data when writing your own simplified JSON by hand.
Field Shape Notes
name String Monster name shown as the statblock title.
meta String Usually size, type, and alignment.
armorClass, hitPoints, speed Strings Core properties shown above abilities.
abilities Object Use STR, DEX, CON, INT, WIS, CHA. Values can include score and modifier.
properties Array Each item can be {"label":"Skills","value":"Perception +4"} or ["Skills","Perception +4"].
features or traits Array Passive traits before action sections. Items can be strings or {"name":"Trait","text":"Description"}.
sections Array Each section has title and features. Common titles are Actions, Bonus Actions, Reactions, Legendary Actions, Lair Actions, and Regional Effects.
columns Array Advanced two-column control. Usually easier to use data-layout="two-column".

Minimum Useful Hand-authored Block

A hand-authored monster can be very small. Only include the fields that matter for that creature. Empty or missing fields are skipped.

<div class="dnd-statblock-data">
{
  "name": "Glass Mote",
  "meta": "Tiny construct, unaligned",
  "armorClass": "14",
  "hitPoints": "5 (2d4)",
  "speed": "0 ft., fly 30 ft. (hover)",
  "abilities": {
    "STR": "3 (-4)",
    "DEX": "18 (+4)",
    "CON": "10 (+0)",
    "INT": "3 (-4)",
    "WIS": "12 (+1)",
    "CHA": "5 (-3)"
  },
  "properties": [
    { "label": "Senses", "value": "blindsight 30 ft., passive Perception 11" },
    { "label": "Challenge", "value": "1/4 (50 XP)" }
  ],
  "sections": [
    {
      "title": "Actions",
      "features": [
        { "name": "Shard Sting", "text": "Melee Weapon Attack: +6 to hit, reach 5 ft., one target. Hit: 6 (1d4 + 4) piercing damage." }
      ]
    }
  ]
}
</div>

Examples

These examples are intentionally copy/pasteable. The .monster examples show wrapper patterns, because the actual exported file can be long. The hand-authored examples show full JSON objects so you can see the structure of properties, traits, sections, and actions.

Hand-authored Monster

This is a normal combat creature with properties and an Actions section. It also enables copy buttons so attack bonuses and dice formulas can be copied during play.

<div class="dnd-statblock-data" data-copy-buttons="true">
{
  "name": "Lantern-Haunt Wight",
  "meta": "Medium undead, lawful evil",
  "armorClass": "15 (ancient mail)",
  "hitPoints": "67 (9d8 + 27)",
  "speed": "30 ft.",
  "abilities": {
    "STR": "16 (+3)",
    "DEX": "12 (+1)",
    "CON": "16 (+3)",
    "INT": "11 (+0)",
    "WIS": "14 (+2)",
    "CHA": "15 (+2)"
  },
  "properties": [
    { "label": "Skills", "value": "Perception +4, Stealth +3" },
    { "label": "Damage Resistances", "value": "necrotic; bludgeoning, piercing, and slashing from nonmagical attacks" },
    { "label": "Senses", "value": "darkvision 60 ft., passive Perception 14" },
    { "label": "Languages", "value": "Common plus one language it knew in life" },
    { "label": "Challenge", "value": "4 (1,100 XP)" }
  ],
  "sections": [
    {
      "title": "Actions",
      "features": [
        {
          "name": "Graveblade",
          "text": "Melee Weapon Attack: +5 to hit, reach 5 ft., one target. Hit: 8 (1d10 + 3) slashing damage plus 7 (2d6) necrotic damage."
        }
      ]
    }
  ]
}
</div>

Social NPC with Utility Actions

Not every statblock needs to be a full combat monster. This example keeps the combat surface small and emphasizes skills, languages, traits, and a reaction.

<div class="dnd-statblock-data">
{
  "name": "Archivist of the Lower Stacks",
  "meta": "Medium humanoid, lawful neutral",
  "armorClass": "12",
  "hitPoints": "33 (6d8 + 6)",
  "speed": "30 ft.",
  "abilities": {
    "STR": "9 (-1)",
    "DEX": "14 (+2)",
    "CON": "12 (+1)",
    "INT": "17 (+3)",
    "WIS": "15 (+2)",
    "CHA": "13 (+1)"
  },
  "properties": [
    { "label": "Saving Throws", "value": "Int +5, Wis +4" },
    { "label": "Skills", "value": "Arcana +5, History +7, Insight +4, Investigation +5" },
    { "label": "Senses", "value": "passive Perception 12" },
    { "label": "Languages", "value": "Common, Draconic, Elvish, two ancient scripts" },
    { "label": "Challenge", "value": "2 (450 XP)" }
  ],
  "features": [
    {
      "name": "Catalogued Memory",
      "text": "The archivist has advantage on Intelligence checks made to recall lore about books, maps, lineages, ruins, and written magic."
    },
    {
      "name": "Quiet Authority",
      "text": "The archivist has advantage on Charisma checks made to calm a creature inside a library, archive, or school."
    }
  ],
  "sections": [
    {
      "title": "Actions",
      "features": [
        {
          "name": "Inkknife",
          "text": "Melee or Ranged Weapon Attack: +4 to hit, reach 5 ft. or range 20/60 ft., one target. Hit: 4 (1d4 + 2) piercing damage."
        },
        {
          "name": "Commanding Whisper",
          "text": "One creature within 30 feet that can hear the archivist must succeed on a DC 13 Wisdom saving throw or have disadvantage on the next attack roll it makes before the end of its next turn."
        }
      ]
    },
    {
      "title": "Reactions",
      "features": [
        {
          "name": "Citation",
          "text": "When a creature the archivist can see makes an Intelligence, Wisdom, or Charisma check, the archivist adds 1d4 to the result."
        }
      ]
    }
  ]
}
</div>

Boss with Legendary Actions

This example shows a larger hand-authored block with traits, actions, reactions, legendary actions, and lair actions. Use data-layout="two-column" when the block is long enough that a single column feels too tall.

<div class="dnd-statblock-data" data-layout="two-column" data-copy-buttons="true">
{
  "name": "Thorn-Crowned Revenant",
  "meta": "Medium undead, neutral evil",
  "armorClass": "17 (thorn mail)",
  "hitPoints": "136 (16d8 + 64)",
  "speed": "30 ft.",
  "abilities": {
    "STR": "18 (+4)",
    "DEX": "14 (+2)",
    "CON": "18 (+4)",
    "INT": "12 (+1)",
    "WIS": "16 (+3)",
    "CHA": "18 (+4)"
  },
  "properties": [
    { "label": "Saving Throws", "value": "Con +8, Wis +7, Cha +8" },
    { "label": "Skills", "value": "Intimidation +8, Perception +7" },
    { "label": "Damage Resistances", "value": "necrotic; bludgeoning, piercing, and slashing from nonmagical attacks" },
    { "label": "Condition Immunities", "value": "charmed, exhaustion, frightened, poisoned" },
    { "label": "Senses", "value": "darkvision 120 ft., passive Perception 17" },
    { "label": "Languages", "value": "Common, Sylvan" },
    { "label": "Challenge", "value": "10 (5,900 XP)" }
  ],
  "features": [
    {
      "name": "Legendary Resistance (3/Day)",
      "text": "If the revenant fails a saving throw, it can choose to succeed instead."
    },
    {
      "name": "Rooted Malice",
      "text": "The ground within 10 feet of the revenant is difficult terrain for its enemies."
    }
  ],
  "sections": [
    {
      "title": "Actions",
      "features": [
        {
          "name": "Multiattack",
          "text": "The revenant makes two Thornblade attacks."
        },
        {
          "name": "Thornblade",
          "text": "Melee Weapon Attack: +8 to hit, reach 5 ft., one target. Hit: 13 (2d8 + 4) slashing damage plus 10 (3d6) necrotic damage."
        },
        {
          "name": "Briar Command (Recharge 5-6)",
          "text": "Each creature of the revenant's choice within 20 feet must make a DC 16 Strength saving throw. On a failure, a creature takes 18 (4d8) piercing damage and is restrained until the end of its next turn."
        }
      ]
    },
    {
      "title": "Reactions",
      "features": [
        {
          "name": "Grasping Root",
          "text": "When a creature the revenant can see moves out of its reach, the creature must succeed on a DC 16 Dexterity saving throw or its speed becomes 0 until the end of the turn."
        }
      ]
    },
    {
      "title": "Legendary Actions",
      "features": [
        "The revenant can take 3 legendary actions, choosing from the options below. Only one legendary action option can be used at a time and only at the end of another creature's turn.",
        {
          "name": "Move",
          "text": "The revenant moves up to half its speed without provoking opportunity attacks."
        },
        {
          "name": "Thorn Lash",
          "text": "The revenant makes one Thornblade attack."
        },
        {
          "name": "Bloom of Rot (Costs 2 Actions)",
          "text": "Each enemy within 10 feet must succeed on a DC 16 Constitution saving throw or take 14 (4d6) poison damage."
        }
      ]
    },
    {
      "title": "Lair Actions",
      "features": [
        "On initiative count 20, losing initiative ties, the revenant can cause one of the following effects:",
        {
          "name": "Black Vines",
          "text": "Vines erupt in a 20-foot square the revenant can see within 120 feet. The area becomes difficult terrain until initiative count 20 on the next round."
        },
        {
          "name": "Whispering Graves",
          "text": "One creature the revenant can see must succeed on a DC 16 Wisdom saving throw or be frightened until the end of its next turn."
        }
      ]
    }
  ]
}
</div>

Multiple Monsters in a Grid

Set "grid": true when a single wrapper contains several small statblocks. This is useful for encounter pages with minions, variants, or summoned creatures.

<div class="dnd-statblock-data">
{
  "grid": true,
  "monsters": [
    {
      "name": "Ash Rat",
      "meta": "Tiny beast, unaligned",
      "armorClass": "12",
      "hitPoints": "7 (2d4 + 2)",
      "speed": "30 ft.",
      "abilities": {
        "STR": "4 (-3)",
        "DEX": "14 (+2)",
        "CON": "12 (+1)",
        "INT": "2 (-4)",
        "WIS": "10 (+0)",
        "CHA": "4 (-3)"
      },
      "properties": [
        { "label": "Senses", "value": "darkvision 30 ft., passive Perception 10" },
        { "label": "Challenge", "value": "1/8 (25 XP)" }
      ],
      "sections": [
        {
          "title": "Actions",
          "features": [
            { "name": "Bite", "text": "Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 4 (1d4 + 2) piercing damage." }
          ]
        }
      ]
    },
    {
      "name": "Cinder Imp",
      "meta": "Tiny fiend, chaotic evil",
      "armorClass": "13",
      "hitPoints": "10 (3d4 + 3)",
      "speed": "20 ft., fly 30 ft.",
      "abilities": {
        "STR": "6 (-2)",
        "DEX": "16 (+3)",
        "CON": "12 (+1)",
        "INT": "10 (+0)",
        "WIS": "11 (+0)",
        "CHA": "13 (+1)"
      },
      "properties": [
        { "label": "Damage Immunities", "value": "fire" },
        { "label": "Senses", "value": "darkvision 60 ft., passive Perception 10" },
        { "label": "Challenge", "value": "1/2 (100 XP)" }
      ],
      "sections": [
        {
          "title": "Actions",
          "features": [
            { "name": "Spark Claw", "text": "Melee Weapon Attack: +5 to hit, reach 5 ft., one target. Hit: 5 (1d4 + 3) slashing damage plus 3 (1d6) fire damage." }
          ]
        }
      ]
    }
  ]
}
</div>

Manual Columns for Hand-authored JSON

Most of the time, data-layout="two-column" is enough. If you want exact control over what goes in each column, use columns. Put "includeCore": true on the column that should contain armor class, hit points, speed, abilities, and property rows.

<div class="dnd-statblock-data" data-layout="wide">
{
  "name": "Mirror-Salt Sentinel",
  "meta": "Large construct, unaligned",
  "armorClass": "18 (natural armor)",
  "hitPoints": "95 (10d10 + 40)",
  "speed": "30 ft.",
  "abilities": {
    "STR": "20 (+5)",
    "DEX": "10 (+0)",
    "CON": "18 (+4)",
    "INT": "6 (-2)",
    "WIS": "12 (+1)",
    "CHA": "8 (-1)"
  },
  "properties": [
    { "label": "Damage Immunities", "value": "poison, psychic" },
    { "label": "Condition Immunities", "value": "charmed, exhaustion, frightened, paralyzed, petrified, poisoned" },
    { "label": "Senses", "value": "darkvision 60 ft., passive Perception 11" },
    { "label": "Languages", "value": "understands the languages of its creator but can't speak" },
    { "label": "Challenge", "value": "6 (2,300 XP)" }
  ],
  "columns": [
    {
      "includeCore": true,
      "features": [
        {
          "name": "Reflective Body",
          "text": "When the sentinel is targeted by a spell attack, roll a d6. On a 6, the attack misses and the attacker becomes the target instead."
        }
      ],
      "sections": [
        {
          "title": "Actions",
          "features": [
            {
              "name": "Multiattack",
              "text": "The sentinel makes two Slam attacks."
            },
            {
              "name": "Slam",
              "text": "Melee Weapon Attack: +8 to hit, reach 10 ft., one target. Hit: 16 (2d10 + 5) bludgeoning damage."
            }
          ]
        }
      ]
    },
    {
      "sections": [
        {
          "title": "Reactions",
          "features": [
            {
              "name": "Saltglass Rebuke",
              "text": "When a creature within 30 feet hits the sentinel with an attack, the sentinel forces that creature to make a DC 15 Constitution saving throw. On a failure, the creature takes 10 (3d6) radiant damage."
            }
          ]
        }
      ]
    }
  ]
}
</div>

Full BookStack Page Snippet

The wrapper can sit between normal BookStack page content. Readers will see the prose and the rendered statblock, not the raw JSON.

<p>The sentinel waits in the ruined west transept. It attacks only if a creature crosses the salt line or touches the sealed door.</p>

<template class="dnd-statblock-data" data-copy-buttons="true">
{
  "name": "Salt-Line Guardian",
  "meta": "Medium construct, unaligned",
  "armorClass": "16",
  "hitPoints": "45 (6d8 + 18)",
  "speed": "30 ft.",
  "abilities": {
    "STR": "16 (+3)",
    "DEX": "12 (+1)",
    "CON": "16 (+3)",
    "INT": "5 (-3)",
    "WIS": "12 (+1)",
    "CHA": "6 (-2)"
  },
  "properties": [
    { "label": "Damage Resistances", "value": "radiant" },
    { "label": "Senses", "value": "darkvision 60 ft., passive Perception 11" },
    { "label": "Challenge", "value": "3 (700 XP)" }
  ],
  "sections": [
    {
      "title": "Actions",
      "features": [
        {
          "name": "Halberd",
          "text": "Melee Weapon Attack: +5 to hit, reach 10 ft., one target. Hit: 8 (1d10 + 3) slashing damage."
        }
      ]
    }
  ]
}
</template>

<p>If the party speaks the old oath aloud, the guardian lowers its weapon and points toward the reliquary stair.</p>

Two-column Tetra-cube Export

This is the main pattern for a large monster exported from Tetra-cube. Replace the placeholder object with the full contents of the saved .monster file.

<template
  class="dnd-monster-data"
  data-layout="two-column"
  data-copy-buttons="true">
{
  "...": "paste the full Tetra-cube .monster JSON here"
}
</template>

Debuggable One-column Tetra-cube Export

This is useful while you are testing a new Tetra-cube monster. Once the monster looks right, you can remove data-show-source if you do not want the source disclosure on the page.

<template
  class="dnd-monster-data"
  data-layout="one-column"
  data-compact="true"
  data-show-source="true"
  data-source-label="Original Tetra-cube JSON">
{
  "...": "paste the full Tetra-cube .monster JSON here"
}
</template>

Tetra-cube-shaped JSON Excerpt

A real .monster export usually has more fields than this, but this excerpt shows the style of data the dnd-monster-data converter understands. Tetra-cube names fields differently from the hand-authored model, such as strPoints, hpText, damagetypes, and legendaries.

<div class="dnd-monster-data" data-copy-buttons="true">
{
  "name": "Emberglass Drake",
  "size": "medium",
  "type": "dragon",
  "alignment": "chaotic neutral",
  "armorClass": 15,
  "hpText": "76 (9d8 + 36)",
  "speed": 30,
  "flySpeed": 60,
  "strPoints": 18,
  "dexPoints": 14,
  "conPoints": 18,
  "intPoints": 8,
  "wisPoints": 12,
  "chaPoints": 15,
  "cr": "5",
  "skills": [
    { "name": "Perception", "note": "ex" },
    { "name": "Stealth" }
  ],
  "damagetypes": [
    { "name": "fire", "type": "i" }
  ],
  "darkvision": 60,
  "languages": [
    { "name": "Draconic", "speaks": true }
  ],
  "abilities": [
    {
      "name": "Heated Scales",
      "desc": "A creature that touches the drake or hits it with a melee attack while within 5 feet takes 3 (1d6) fire damage."
    }
  ],
  "actions": [
    {
      "name": "Bite",
      "desc": "_Melee Weapon Attack:_ [STR ATK] to hit, reach 5 ft., one target. _Hit:_ [STR 1D10] piercing damage plus [2D6] fire damage."
    },
    {
      "name": "Glassfire Breath (Recharge 5-6)",
      "desc": "The drake exhales fire in a 30-foot cone. Each creature in that area must make a [CON SAVE] Dexterity saving throw, taking [8D6] fire damage on a failed save, or half as much damage on a successful one."
    }
  ]
}
</div>

Tetra-cube Token Support

Tetra-cube descriptions often contain bracket tokens. The renderer resolves the following patterns when converting .monster data.

Token Example Output Meaning
[MON] the dragon Short monster name.
[MONS] dragons Plural monster name.
[STR], [DEX + 2] +10, +4 Ability modifier, optionally adjusted.
[STR ATK], [DEX ATK - 1] +17 Ability modifier plus proficiency, optionally adjusted.
[WIS SAVE], [CHA SAVE + 2] DC 18 Save DC using 8 + ability modifier + proficiency, optionally adjusted.
[STR 2D6], [DEX 1D8 + 2] 17 (2d6 + 10) Average dice damage plus an ability modifier.
[3D6], [2D10 + 4] 10 (3d6) Plain dice average and display formula.

Token Example in a Tetra-cube Description

{
  "name": "Bite",
  "desc": "_Melee Weapon Attack:_ [STR ATK] to hit, reach 10 ft., one target. _Hit:_ [STR 2D10] piercing damage."
}

Formatting Notes

  • **bold** text is rendered as bold.
  • _italic_ text is rendered as italic.
  • Lines beginning with > render as hanging-indent lines, useful for spell-style lists or nested effects.
  • Melee Weapon Attack:, Ranged Weapon Attack:, and Hit: are styled like 5e labels.
  • Print mode hides copy controls and source/debug disclosures.
  • Dark mode is supported through BookStack's .dark-mode class and matching color variables.

Troubleshooting

The JSON text appears on the page instead of a statblock.

The CSS or script is probably not installed in BookStack custom head content, or the wrapper class is wrong. Check for dnd-monster-data or dnd-statblock-data.

BookStack will not save the wrapper.

Try source/HTML mode. If template is stripped, switch to the plain div wrapper. Keep the JSON inline and do not use <script type="application/json">.

An error box says "Unexpected end of JSON input."

The pasted JSON is incomplete, usually missing a closing brace or bracket. The error box includes a line, column, and excerpt near the problem.

The block is too wide or too cramped.

Use data-layout="one-column" for narrow pages, data-layout="two-column" for long monsters, data-layout="wide" for large desktop pages, or data-compact="true" for dense entries.

Copy buttons are visible but not needed.

Remove data-copy-buttons="true". Copy controls are opt-in, so plain pasted .monster wrappers will not show them unless requested.

I want to inspect the original data after render.

Add data-show-source="true". The original JSON will be hidden in a collapsed disclosure after the statblock.

For most monsters, use this pattern. It keeps the page clean, supports copy buttons, and preserves the original .monster data for debugging.

<template
  class="dnd-monster-data"
  data-copy-buttons="true"
  data-show-source="true"
  data-source-label="Original .monster JSON">
{
  "...": "paste the full .monster JSON here"
}
</template>

If BookStack removes template, use the same attributes on <div class="dnd-monster-data">.

This is a test.

{ "name": "Lantern-Haunt Wight", "meta": "Medium undead, lawful evil", "theme": "undead", "layout": "wide", "armorClass": "15 (ancient mail)", "hitPoints": "67 (9d8 + 27)", "speed": "30 ft.", "abilities": { "STR": "16 (+3)", "DEX": "12 (+1)", "CON": "16 (+3)", "INT": "11 (+0)", "WIS": "14 (+2)", "CHA": "15 (+2)" }, "properties": [ { "label": "Skills", "value": "Perception +4, Stealth +3" }, { "label": "Damage Resistances", "value": "necrotic; bludgeoning, piercing, and slashing from nonmagical attacks" }, { "label": "Senses", "value": "darkvision 60 ft., passive Perception 14" }, { "label": "Languages", "value": "Common plus one language it knew in life" }, { "label": "Challenge", "value": "4 (1,100 XP)" } ], "sections": [ { "title": "Actions", "features": [ { "name": "Graveblade.", "text": "Melee Weapon Attack: +5 to hit, reach 5 ft., one target. Hit: 8 (1d10 + 3) slashing damage plus 7 (2d6) necrotic damage." } ] } ] }
{"name":"Ancient Red Dragon","size":"gargantuan","type":"Dragon","tag":"","alignment":"chaotic evil","hitDice":"28","armorName":"natural armor","shieldBonus":0,"natArmorBonus":12,"otherArmorDesc":"22 (natural armor)","speed":"40","burrowSpeed":"0","climbSpeed":"40","flySpeed":"80","hover":false,"swimSpeed":"0","customHP":false,"customSpeed":false,"hpText":"546 (28d20 + 252)","speedDesc":"40 ft., climb 40 ft., fly 80 ft.","strPoints":"30","dexPoints":"10","conPoints":"29","intPoints":"18","wisPoints":"15","chaPoints":"23","blindsight":"60","blind":false,"darkvision":"120","tremorsense":"0","truesight":"0","telepathy":0,"cr":"24","customCr":"24 (62,000 XP)","customProf":7,"isLegendary":true,"legendariesDescription":"The dragon can take 3 legendary actions, choosing from the options below. Only one legendary action option can be used at a time and only at the end of another creature's turn. The dragon regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the ancient red dragon can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the ancient red dragon can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The ancient red dragon can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the ancient red dragon's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the ancient red dragon's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the ancient red dragon dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Legendary Resistance (3/Day)","desc":"If the dragon fails a saving throw, it can choose to succeed instead."}],"actions":[{"name":"Multiattack","desc":"The dragon can use its Frightful Presence. It then makes three attacks: one with its bite and two with its claws."},{"name":"Bite","desc":"_Melee Weapon Attack:_ +17 to hit, reach 15 ft., one target. _Hit:_ 21 (2d10 + 10) piercing damage plus 14 (4d6) fire damage."},{"name":"Claw","desc":"_Melee Weapon Attack:_ +17 to hit, reach 10 ft., one target. _Hit:_ 17 (2d6 + 10) slashing damage."},{"name":"Tail","desc":"_Melee Weapon Attack:_ +17 to hit, reach 20 ft., one target. _Hit:_ 19 (2d8 + 10) bludgeoning damage."},{"name":"Frightful Presence","desc":"Each creature of the dragon's choice that is within 120 feet of the dragon and aware of it must succeed on a DC 21 Wisdom saving throw or become frightened for 1 minute. A creature can repeat the saving throw at the end of each of its turns, ending the effect on itself on a success. If a creature's saving throw is successful or the effect ends for it, the creature is immune to the dragon's Frightful Presence for the next 24 hours."},{"name":"Fire Breath (Recharge 5-6)","desc":"The dragon exhales fire in a 90-foot cone. Each creature in that area must make a DC 24 Dexterity saving throw, taking 91 (26d6) fire damage on a failed save, or half as much damage on a successful one."}],"bonusActions":[],"reactions":[],"legendaries":[{"name":"Detect","desc":"The dragon makes a Wisdom (Perception) check."},{"name":"Tail Attack","desc":"The dragon makes a tail attack."},{"name":"Wing Attack (Costs 2 Actions)","desc":"The dragon beats its wings. Each creature within 15 ft. of the dragon must succeed on a DC 25 Dexterity saving throw or take 17 (2d6 + 10) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."}],"mythics":[],"lairs":[],"regionals":[],"sthrows":[{"name":"dex","order":1},{"name":"con","order":2},{"name":"wis","order":4},{"name":"cha","order":5}],"skills":[{"name":"perception","stat":"wis","note":" (ex)"},{"name":"stealth","stat":"dex"}],"damagetypes":[{"name":"fire","note":" (Immune)","type":"i"}],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Draconic","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":true,"separationPoint":3,"damage":[]}