Skip to content
fvtt-dice-rolls logo

Foundry VTT Dice Rolls

fvtt-dice-rolls

This skill should be used when implementing dice rolling, creating Roll formulas, sending rolls to chat with toMessage, preparing getRollData, creating custom dice types, or handling roll modifiers like advantage/disadvantage. Covers Roll class, evaluation, and common patterns.

SKILL.md

Full skill instructions

Foundry VTT Dice Rolls

Domain: Foundry VTT Module/​System Development Status: Production-Ready Last Updated: 2026-01-04

Overview

Foundry VTT provides a powerful dice rolling system built around the Roll class. Understanding this system is essential for implementing game mechanics.

When to Use This Skill

  • Creating roll formulas with variable substitution
  • Implementing attack rolls, damage rolls, saving throws
  • Sending rolls to chat with proper speaker/​flavor
  • Preparing actor/​item roll data with getRollData()
  • Creating custom dice types for specific game systems
  • Using roll modifiers (keep, drop, explode, reroll)

Roll Class Basics

Constructor

const roll = new Roll(formula, data, options);
  • formula: Dice expression string (e.g., "2d20kh + @prof")
  • data: Object for @ variable substitution
  • options: Optional configuration
const roll = new Roll("2d20kh + @prof + @strMod", {
  prof: 2,
  strMod: 4
});

Formula Syntax

// Basic dice
"1d20"          // Roll one d20
"4d6"           // Roll four d6

// Variables with @ syntax
"1d20 + @abilities.str.mod"
"1d20 + @prof"

// Nested paths
"@classes.barbarian.levels"
"@abilities.dex.mod"

// Parenthetical (dynamic dice count)
"(@level)d6"    // Roll [level] d6s

// Dice pools
"{4d6kh3, 4d6kh3, 4d6kh3}"  // Multiple separate rolls

Roll Evaluation

Async evaluate() - REQUIRED

const roll = new Roll("1d20 + 5");
await roll.evaluate();

console.log(roll.result);  // "15 + 5"
console.log(roll.total);   // 20

Critical: roll.total is undefined until evaluated.

Evaluation Options

await roll.evaluate({
  maximize: true,    // All dice roll max value
  minimize: true,    // All dice roll min value
  allowStrings: true // Don't error on string terms
});

Sync Evaluation (Deterministic Only)

// Only for maximize/​minimize (deterministic)
roll.evaluateSync({ strict: true });

// With strict: false, non-deterministic = 0
roll.evaluateSync({ strict: false });

Roll.toMessage()

Sends a roll to chat as a ChatMessage.

Basic Usage

await roll.toMessage();

With Options

await roll.toMessage({
  speaker: ChatMessage.getSpeaker({ actor: this.actor }),
  flavor: "Attack Roll",
  user: game.user.id
}, {
  rollMode: game.settings.get("core", "rollMode")
});

Roll Modes

ModeCommandVisibility
Public/​rollEveryone
GM/​gmrollRoller + GM
Blind/​blindrollGM only
Self/​selfrollRoller only

Always respect user's roll mode:

rollMode: game.settings.get("core", "rollMode")

getRollData()

Prepares data context for roll formulas.

Actor getRollData()

getRollData() {
  // Always return a COPY
  const data = foundry.utils.deepClone(this.system);

  // Add shortcuts
  data.lvl = data.details.level;

  // Flatten ability mods for easy access
  for (const [key, ability] of Object.entries(data.abilities)) {
    data[key] = ability.mod;  // @str, @dex, etc.
  }

  return data;
}

Item getRollData()

Merge item and actor data:

getRollData() {
  const data = foundry.utils.deepClone(this.system);

  if (!this.actor) return data;

  // Merge actor's roll data
  return foundry.utils.mergeObject(
    this.actor.getRollData(),
    data
  );
}

Debugging Roll Data

// In console with token selected:
console.log(canvas.tokens.controlled[0].actor.getRollData());

Roll Modifiers

Keep/​Drop

"4d6kh3"   // Keep 3 highest (ability scores)
"4d6kl3"   // Keep 3 lowest
"4d6dh1"   // Drop 1 highest
"4d6dl1"   // Drop 1 lowest
"2d20kh"   // Advantage (keep highest)
"2d20kl"   // Disadvantage (keep lowest)

Exploding Dice

"5d10x"    // Explode on max (10)
"5d10x8"   // Explode on 8+
"2d10xo"   // Explode once per die

Reroll

"1d20r1"    // Reroll 1s (once)
"1d20r<3"   // Reroll below 3 (once)
"1d20rr<3"  // Recursive reroll while < 3

Count Successes

"10d6cs>4"  // Count successes > 4
"10d6cf<2"  // Count failures < 2

Min/​Max

"1d20min10"  // Minimum result 10
"1d20max15"  // Maximum result 15

Common Patterns

Attack Roll

async rollAttack() {
  const rollData = this.actor.getRollData();

  const parts = ["1d20"];
  if (this.system.proficient) parts.push("@prof");
  if (this.system.ability) parts.push(`@${this.system.ability}.mod`);
  if (this.system.attackBonus) parts.push(this.system.attackBonus);

  const formula = parts.join(" + ");
  const roll = new Roll(formula, rollData);
  await roll.evaluate();

  return roll.toMessage({
    speaker: ChatMessage.getSpeaker({ actor: this.actor }),
    flavor: `${this.name} - Attack Roll`,
    rollMode: game.settings.get("core", "rollMode")
  });
}

Damage Roll (with Critical)

async rollDamage(critical = false) {
  const rollData = this.actor.getRollData();

  let formula = this.system.damage.formula;

  // Add ability mod
  if (this.system.damage.ability) {
    formula += ` + @${this.system.damage.ability}.mod`;
  }

  // Double dice on critical
  if (critical) {
    formula = formula.replace(/(\d+)d(\d+)/​g, (m, num, faces) => {
      return `${num * 2}d${faces}`;
    });
  }

  const roll = new Roll(formula, rollData);
  await roll.evaluate();

  return roll.toMessage({
    speaker: ChatMessage.getSpeaker({ actor: this.actor }),
    flavor: `${this.name} - ${critical ? "Critical " : ""}Damage`,
    rollMode: game.settings.get("core", "rollMode")
  });
}

Ability Check with Advantage/​Disadvantage

async rollAbility(abilityId, { advantage = false, disadvantage = false } = {}) {
  const rollData = this.actor.getRollData();

  let dieFormula = "1d20";
  if (advantage && !disadvantage) dieFormula = "2d20kh";
  if (disadvantage && !advantage) dieFormula = "2d20kl";

  const formula = `${dieFormula} + @abilities.${abilityId}.mod`;
  const roll = new Roll(formula, rollData);
  await roll.evaluate();

  return roll.toMessage({
    speaker: ChatMessage.getSpeaker({ actor: this.actor }),
    flavor: `${CONFIG.abilities[abilityId]} Check`,
    rollMode: game.settings.get("core", "rollMode")
  });
}

Sheet Rollable Button

// In activateListeners
html.on("click", ".rollable", this._onRoll.bind(this));

async _onRoll(event) {
  event.preventDefault();
  const element = event.currentTarget;
  const { roll: formula, label } = element.dataset;

  if (!formula) return;

  const roll = new Roll(formula, this.actor.getRollData());
  await roll.evaluate();

  return roll.toMessage({
    speaker: ChatMessage.getSpeaker({ actor: this.actor }),
    flavor: label || "Roll",
    rollMode: game.settings.get("core", "rollMode")
  });
}

Template:

<a class="rollable" data-roll="1d20 + @str" data-label="Strength Check">
  <i class="fas fa-dice-d20"></​i> Roll
</​a>

Custom Dice

Custom Die Class

export class StressDie extends foundry.dice.terms.Die {
  static DENOMINATION = "s";  // Use as "1ds"

  async evaluate(options = {}) {
    await super.evaluate(options);

    // Custom logic: explode on 6, panic on 1
    for (const result of this.results) {
      if (result.result === 6) result.exploded = true;
      if (result.result === 1) result.panic = true;
    }

    return this;
  }
}

Custom Roll Class

export class CustomRoll extends Roll {
  static CHAT_TEMPLATE = "systems/​mysystem/​templates/​roll.hbs";

  get successes() {
    return this.dice.reduce((sum, die) => {
      return sum + die.results.filter(r => r.success).length;
    }, 0);
  }
}

Registration

Hooks.once("init", () => {
  CONFIG.Dice.terms.s = StressDie;
  CONFIG.Dice.rolls.push(CustomRoll);
});

Critical: Register custom rolls or they won't reconstruct from chat messages.

Common Pitfalls

1. Using total Before evaluate()

// WRONG - total is undefined
const roll = new Roll("1d20");
console.log(roll.total);  // undefined!

// CORRECT
const roll = new Roll("1d20");
await roll.evaluate();
console.log(roll.total);  // 15

2. Ignoring Roll Mode

// WRONG - always public
roll.toMessage();

// CORRECT - respects user setting
roll.toMessage({}, {
  rollMode: game.settings.get("core", "rollMode")
});

3. Modifying getRollData() Return

// WRONG - modifies document data
getRollData() {
  return this.system;  // Direct reference!
}

// CORRECT - return a copy
getRollData() {
  return foundry.utils.deepClone(this.system);
}

4. Stale Roll Data

// WRONG - data captured once
const rollData = this.actor.getRollData();
// ...actor updates...
new Roll("1d20 + @prof", rollData);  // Stale!

// CORRECT - get fresh data
new Roll("1d20 + @prof", this.actor.getRollData());

5. Unvalidated User Input

// UNSAFE
const roll = new Roll(userInput);

// SAFER - validate first
if (!Roll.validate(userInput)) {
  ui.notifications.error("Invalid roll formula");
  return;
}
const roll = new Roll(userInput, rollData);

6. Forgetting to Register Custom Rolls

// WRONG - rolls break on reload
class MyRoll extends Roll {}

// CORRECT - register with CONFIG
class MyRoll extends Roll {}
CONFIG.Dice.rolls.push(MyRoll);

7. Async in preCreate Hooks

// PROBLEMATIC - hooks can't reliably await
Hooks.on("preCreateItem", async (doc, data) => {
  const roll = new Roll("1d20");
  await roll.evaluate();  // May fail!
});

// BETTER - use onCreate
Hooks.on("createItem", async (doc, options, userId) => {
  if (userId !== game.user.id) return;
  const roll = new Roll("1d20");
  await roll.evaluate();  // Safe
});

Implementation Checklist

  • Always await roll.evaluate() before accessing roll.total
  • Use getRollData() returning a deep clone
  • Pass rollMode: game.settings.get("core", "rollMode") to toMessage
  • Use ChatMessage.getSpeaker({ actor }) for proper speaker
  • Validate user-provided formulas with Roll.validate()
  • Register custom Roll/​Die classes in CONFIG.Dice
  • Add flavor text describing the roll
  • Use @ syntax for variable substitution in formulas

References


Last Updated: 2026-01-04 Status: Production-Ready Maintainer: ImproperSubset

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View