API: Request Roll - Drowbe/coffee-pub-blacksmith GitHub Wiki
Request a Roll API Documentation
Audience: Developers integrating with Blacksmith and leveraging the exposed API.
Overview
The Request a Roll API lets external modules open Blacksmith’s Request a Roll (Skill Check) dialog programmatically and optionally pre-fill its state. The dialog is the same one opened by the “Request a Roll” toolbar tool and menubar entry: it lets the GM choose a roll type (skill, ability, save, or tool), select actors (challengers/defenders), set a DC, and send a roll request to chat.
Use this API when your module needs to:
- Open the Request a Roll dialog from a button, macro, or hook
- Create a roll request without opening the dialog (silent mode) — post the request card to chat immediately
- Pre-select a roll type (e.g. Perception, Stealth, Strength save)
- Set a default DC or actor filter (selected tokens vs party)
- Pre-check the "Group roll" option (e.g. for party group checks)
- Override the dialog title for context (e.g. “Spot the trap”)
Getting Started
Accessing the API
// Via game.modules (no imports – use in browser console or other modules)
const api = game.modules.get('coffee-pub-blacksmith')?.api;
const dialog = api?.openRequestRollDialog({ initialSkill: 'perception' });
// Or via Blacksmith API bridge (async – waits for API ready)
import { BlacksmithAPI } from '/modules/coffee-pub-blacksmith/api/blacksmith-api.js';
const dialog = await BlacksmithAPI.openRequestRollDialog({
title: 'Spot the trap',
initialType: 'skill',
initialValue: 'perception',
dc: 15
});
API Availability Check
const api = game.modules.get('coffee-pub-blacksmith')?.api;
if (!api?.openRequestRollDialog) {
console.warn('Blacksmith Request a Roll API not available');
return;
}
api.openRequestRollDialog({ initialSkill: 'stealth' });
API Reference
openRequestRollDialog(options?)
Opens the Request a Roll (Skill Check) dialog. Optionally pass an options object to pre-fill the dialog (roll type, DC, actor filter, title). The dialog is the same Application used by the toolbar and menubar; users can change any value before submitting.
Parameters
| Parameter | Type | Description |
|---|---|---|
options |
Object |
Optional. All properties are optional. |
options.silent |
boolean |
If true, the dialog is not opened; the roll request is created immediately and posted to chat. Requires initialValue or initialSkill. Actors come from initialFilter ('party' | 'selected') or from options.actors. Returns a Promise resolving to { message, messageId } (module API returns that Promise; drop-in API resolves with it). If no actors are found (e.g. no tokens on the scene or none matching the filter), the API falls back to opening the dialog instead of throwing, and the Promise resolves with { message: null, messageId: null, fallbackDialog } so callers can detect the fallback. |
options.title |
string |
Override the dialog window title (e.g. "Spot the trap"). |
options.initialType |
string |
Pre-select the roll type: 'skill', 'ability', or 'save'. |
options.initialValue |
string |
Id or friendly name for that type. You can pass the system’s CONFIG id (e.g. 'prc' for Perception in D&D 5e) or a friendly/localized name (e.g. 'perception'); the dialog resolves it automatically. Skills: 'perception', 'stealth', 'insight', etc.; abilities: 'str', 'dex', 'con', 'int', 'wis', 'cha'; saves: same as abilities plus 'death'. |
options.initialSkill |
string |
Legacy. Same as initialType: 'skill' with initialValue set to this (e.g. 'perception'). |
options.dc |
number or string |
Default DC value shown in the dialog’s DC field. |
options.initialFilter |
string |
Which actor list is active: 'selected' (only selected tokens) or 'party' (party filter). When 'party', all visible party actors are also pre-selected as challengers. |
options.groupRoll |
boolean |
If true, the "Group roll" checkbox is checked (multiple challengers roll as a group); if false, it is unchecked. When omitted: in dialog mode the checkbox is unchecked; in silent mode, if multiple actors are supplied (via actors or initialFilter), group roll defaults to true unless you pass groupRoll: false. |
options.situationalBonus |
number |
Optional. Pre-filled in the Roll Configuration window’s "Situational Bonus" field. When using initialFilter or the dialog, this value applies to all actors. When using options.actors, this is the default for any actor that does not specify its own situationalBonus. |
options.customModifier |
string |
Optional. Pre-filled in the Roll Configuration window’s "Custom Modifier" field (e.g. "+2", "-1"). Same scope as situationalBonus: all actors when using filter/dialog, or default when using options.actors. |
options.callback |
Function |
Callback used by the dialog (if applicable). |
options.onRollComplete |
Function |
Callback invoked each time a roll result is delivered to the chat card on the local client that registered it. Receives one argument: (payload) where payload is { messageId, message, messageData, tokenId, result, allComplete, requesterId, rollerUserId }. Called once per roll; unregistered when allComplete is true. For cross-client/GM-authoritative handling, use the global hook Hooks.on('blacksmith.requestRollComplete', ...). |
options.actors |
Array |
Optional actor list. When silent mode is used, this is the preferred way to supply actors. Each element may be (1) a Foundry Actor document (or { id: actorId, name? }), or (2) a token-centric object { id: tokenId, actorId, name?, group?, situationalBonus?, customModifier? }. Per-actor modifiers: when you pass an array of actor objects, each may include situationalBonus (number) and customModifier (string) for that actor only. If omitted for an actor, the global options.situationalBonus and options.customModifier are used. Use this when only some actors get a bonus (e.g. one of two players has +2 for harvest). When not silent, the dialog can use this to pre-fill if it supports it. |
Returns
- Module API (
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog(options)): Whensilentis not used:Application(the opened dialog). Whenoptions.silent === true:Promise<{ message: ChatMessage, messageId: string }>. - Drop-in API (
BlacksmithAPI.openRequestRollDialog(options)):Promise<Application>when not silent, orPromise<{ message, messageId }>whenoptions.silent === true.
Examples
// Open with no pre-fill (same as clicking the toolbar button)
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog();
// Pre-select Perception check
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog({
initialSkill: 'perception'
});
// Same using initialType / initialValue
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog({
initialType: 'skill',
initialValue: 'perception'
});
// Pre-select Strength saving throw
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog({
initialType: 'save',
initialValue: 'str'
});
// Open with custom title, default DC 15, party filter, and group roll checked
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog({
title: 'Spot the trap',
initialType: 'skill',
initialValue: 'perception',
dc: 15,
initialFilter: 'party',
groupRoll: true
});
// Roll for harvest with +2 situational bonus (pre-filled in Roll Configuration window)
game.modules.get('coffee-pub-blacksmith').api.openRequestRollDialog({
title: 'Forage for Components',
initialType: 'skill',
initialValue: 'survival',
dc: 12,
situationalBonus: 2,
customModifier: '+2' // optional; e.g. tool or circumstance
});
// Two actors: only one gets a situational bonus (e.g. only Alice has the tool)
const api = game.modules.get('coffee-pub-blacksmith')?.api;
await api?.openRequestRollDialog({
silent: true,
title: 'Forage for Components',
initialType: 'skill',
initialValue: 'survival',
dc: 12,
actors: [
{ actorId: aliceActor.id, tokenId: aliceToken.id, name: 'Alice', situationalBonus: 2, customModifier: '+2' },
{ actorId: bobActor.id, tokenId: bobToken.id, name: 'Bob' }
]
});
// Via BlacksmithAPI (async)
const dialog = await BlacksmithAPI.openRequestRollDialog({
title: 'Stealth check',
initialSkill: 'stealth',
dc: 12,
initialFilter: 'selected'
});
Silent mode: create roll request without opening the dialog
Pass silent: true to create the roll request and post it to chat immediately, without showing the Request a Roll window. You must supply a roll type (e.g. initialType + initialValue, or initialSkill). Actors are resolved from initialFilter ('party' or 'selected') or from an explicit actors array. The return value is a Promise that resolves to { message, messageId }.
// Module API (returns a Promise when silent)
const api = game.modules.get('coffee-pub-blacksmith')?.api;
const { message, messageId } = await api.openRequestRollDialog({
silent: true,
title: 'Spot the trap',
initialType: 'skill',
initialValue: 'perception',
dc: 15,
initialFilter: 'party',
groupRoll: true,
onRollComplete: (payload) => console.log('Roll complete', payload)
});
// Drop-in API
const { message, messageId } = await BlacksmithAPI.openRequestRollDialog({
silent: true,
initialSkill: 'stealth',
dc: 12,
initialFilter: 'selected'
});
Silent mode supports the same options as the dialog (e.g. dc, title, groupRoll, showDC, showRollExplanation, isCinematic, rollMode, onRollComplete). It does not support contested rolls or tool proficiencies; use the full dialog for those.
Receiving roll results in your module (onRollComplete)
Pass onRollComplete when opening the dialog to be notified each time a roll result is delivered to the chat card (when a player rolls). The callback is invoked with a single payload object:
api.openRequestRollDialog({
title: 'Spot the trap',
initialType: 'skill',
initialValue: 'perception',
dc: 15,
initialFilter: 'party',
onRollComplete: (payload) => {
// payload: { messageId, message, messageData, tokenId, result, allComplete, requesterId, rollerUserId }
const { messageData, tokenId, result, allComplete } = payload;
console.log('Roll received:', tokenId, result?.total);
if (allComplete) {
console.log('All rolls complete:', messageData.actors);
}
}
});
Receiving roll results across clients (recommended for GM-authoritative workflows)
Use the global hook below when the request may be initiated by players but your module resolves state on the GM:
Hooks.on('blacksmith.requestRollComplete', (payload) => {
// payload: { messageId, message, messageData, tokenId, result, allComplete, requesterId, rollerUserId }
if (!game.user.isGM) return;
const { messageId, tokenId, result, allComplete, messageData, requesterId, rollerUserId } = payload;
console.log('Request roll update', { messageId, tokenId, total: result?.total, allComplete, requesterId, rollerUserId });
if (allComplete) {
// Run final GM-authoritative resolution here
}
});
Roll Type and Value Reference
You can pass either the system’s CONFIG id (e.g. D&D 5e uses prc for Perception) or a friendly/localized name (e.g. perception); the dialog resolves it automatically.
Skills (initialType: 'skill')
Use skill ids or names as initialValue, for example:
perception(orprcin D&D 5e),stealth,insight,investigation,athletics,acrobatics,arcana,deception,history,intimidation,medicine,nature,performance,persuasion,religion,sleight_of_hand,survival
Abilities (initialType: 'ability')
Use ability ids: str, dex, con, int, wis, cha.
Saves (initialType: 'save')
Use the same ability ids plus death for death saves: str, dex, con, int, wis, cha, death.
Usage Examples
Example: Party perception check, DC 12
A module that wants to ask for a party perception check at DC 12 can open the dialog like this:
const api = game.modules.get('coffee-pub-blacksmith')?.api;
if (api?.openRequestRollDialog) {
api.openRequestRollDialog({
initialType: 'skill',
initialValue: 'perception',
initialFilter: 'party',
dc: 12,
groupRoll: true
});
}
Optional: set a custom title (e.g. title: 'Spot the ambush'). The dialog opens with the Party filter active, all party actors pre-selected as challengers, Perception pre-selected as the roll type, DC 12, and the "Group roll" checkbox checked; the user only needs to click the request button (or change anything first).
Example 1: Button that opens a Perception check
// In your Application or HTML button handler
document.getElementById('btn-spot-trap').addEventListener('click', () => {
const api = game.modules.get('coffee-pub-blacksmith')?.api;
if (!api?.openRequestRollDialog) return;
api.openRequestRollDialog({
title: 'Spot the trap',
initialSkill: 'perception',
dc: 15,
initialFilter: 'party'
});
});
Example 2: Macro that opens Request a Roll with a pre-selected save
// Foundry macro: open Request a Roll with Constitution save pre-selected
const api = game.modules.get('coffee-pub-blacksmith')?.api;
if (api?.openRequestRollDialog) {
api.openRequestRollDialog({
initialType: 'save',
initialValue: 'con',
dc: 12
});
} else {
ui.notifications.warn('Blacksmith Request a Roll API not available.');
}
Example 3: Using the drop-in API from another module
import { BlacksmithAPI } from '/modules/coffee-pub-blacksmith/api/blacksmith-api.js';
async function requestStealthCheck() {
const dialog = await BlacksmithAPI.openRequestRollDialog({
title: 'Stealth',
initialSkill: 'stealth',
dc: 10,
initialFilter: 'selected'
});
// dialog is the SkillCheckDialog Application instance
return dialog;
}
Example 4: Hook that opens the dialog with context
Hooks.on('my-module.requestRoll', (context) => {
const api = game.modules.get('coffee-pub-blacksmith')?.api;
if (!api?.openRequestRollDialog) return;
api.openRequestRollDialog({
title: context.title || 'Request a Roll',
initialType: context.type || 'skill',
initialValue: context.value || 'perception',
dc: context.dc,
initialFilter: context.filter || 'party'
});
});
Related Documentation
api-menubar.md– Menubar and toolbar registration (the “Request Roll” entry uses the same dialog)api-toolbar.md– Toolbar tool registrationarchitecture-rolls.md– Roll and skill check flow architecture