Skip to content

roll()

Roll dice, return the total, and — unless you say otherwise — show the roll in chat with 3D dice.

Part of the Action API · Function reference

Signature

roll(expression: string): number
roll(expression: string, pipeToChat: boolean): number
roll(expression: string, options: {
  label?: string;
  text?: string;
  silent?: boolean;
  pipeToChat?: boolean;
}): number

roll is synchronous. It returns a number, not a promise. await roll(...) is harmless but does nothing.

Parameters

Parameter Type Default Description
expression string — The dice expression. See Expressions below.
pipeToChat boolean true Shorthand second argument. false rolls silently — no chat row, no 3D dice.
options.label string none A header rendered above the roll, e.g. 'Perception'. Plain text; HTML is escaped.
options.text string none Supplemental text on the same chat row. {result} is replaced with the rendered roll; without the placeholder the roll is appended after your text. Plain text; HTML is escaped.
options.silent boolean false true rolls silently — no chat row, no 3D dice — even when label/text are given. The readable spelling of the two below (v0.86.0).
options.pipeToChat boolean true The older spelling: false means the same as silent: true. When both are given, silent wins.

Returns

number — the total, immediately.

Negative totals are possible (roll('1d4-10')). The value is always a number: an expression the roller cannot parse returns 0 rather than throwing.

Expressions

An expression is a series of terms joined by + and -. Each term is either a dice group, a plain number, or a property name.

roll('1d20')          // one d20
roll('d20')           // the count is optional
roll('2d6+3')         // dice plus a flat modifier
roll('1d8+1d6-2')     // as many terms as you like
roll('1d20+strength') // a property name from the acting token

Dice groups are NdS — N dice of S sides, with N defaulting to 1. Any number of sides works, including odd ones like 1d7. A silent roll (pipeToChat: false) caps a group at 100 dice and returns 0 beyond that; the chat path has no such cap, so keep groups sane rather than relying on either behaviour.

Only + and - are supported. There is no *, /, or parentheses, and no keep-highest / drop-lowest / exploding notation. Do that arithmetic in JavaScript instead:

const total = roll('2d6', false) * 2;

Property names are resolved against the acting token, case-insensitively, by property name, alias, or id — the same resolution getProperty() uses, so str, Strength, and a system-prefixed id all find the same value. The stored value is substituted: a Strength of 18 puts 18 into the expression, not a derived modifier. If your system wants a modifier, store one as its own property and roll that.

A name that resolves to nothing, or to something non-numeric, is left in the expression as-is — which usually means the term is dropped from the total. A bare d20-shaped word is always treated as dice, never as a property.

Chat output

Expression What appears in chat
One group of standard dice, e.g. 2d6+3 A rich dice row, plus 3D dice tinted with the acting token's chat colour
Several terms, e.g. 1d20+4+17 A plain dice line with the correct total; no 3D animation
Non-standard dice, e.g. 1d7 A plain dice line. The 3D dice only know d4, d6, d8, d10, d12, d20, and d100
No dice at all, e.g. strength Nothing — the value is returned silently, unless you passed label or text

The 3D dice are fire-and-forget: they never change or delay the number in chat.

Every message a single action sends — dice rows included — is grouped into one bordered entry in the chat log, so a five-roll action does not spam it.

Examples

A plain roll

const attack = roll('1d20+attackBonus');

A labelled roll

roll('1d20', { label: 'Perception' });

A roll inside a sentence

roll('2d6+3', {
  label: 'Longsword',
  text: `${token.name} swings and connects for {result}.`,
});

A silent roll you format yourself

const damage = roll('3d6', false);
sendMessage(`<b>${token.name}</b> deals ${damage} points of searing damage.`);

Attack, then damage on a hit

const attack = roll('1d20+attackBonus', { label: 'Attack' });
if (target && attack >= (target.getProperty('ac') ?? 10)) {
  target.applyDamage(roll('1d8+2', { label: 'Damage' }), 'slashing');
} else {
  sendMessage('A miss.');
}

Notes and gotchas

  • Don't put a dice expression in sendMessage() text if you already rolled it. sendMessage() rolls any dice expression it finds, so sendMessage('You take 2d6') produces a second, unrelated roll. Pass the number you already have, or use roll(expr, { text }).
  • label and text are escaped. They are plain text by design. For HTML, roll silently and call sendMessage().
  • Each call is one roll. Calling roll() twice gives two different results; store the total in a variable if you need it more than once.
  • The Test panel really rolls. Only chat output is captured; the numbers are real.

See also

  • rollDetailed() — the same maths, with the individual dice, and never any chat output
  • requestRoll() — ask another player to roll
  • sendMessage() — write your own chat output
  • Dice — how dice work everywhere else in the app