token.requestRoll()
Ask the player who owns a token to make a roll, and wait for the number.
Part of the Action API · Function reference
Available on token, target, and every entry of targets. It is almost
always called on a target — a saving throw or an opposed check.
Signature
token.requestRoll(expression: string, options?: {
label?: string;
text?: string;
pipeToChat?: boolean;
}): Promise<number | null>
Asynchronous — await it.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
expression |
string |
— | The dice expression to roll. Property names resolve against that token's stats, not the caster's. Same grammar as roll(). |
options.label |
string |
none | Shown in the owner's prompt and as a header on the resulting chat row, e.g. 'Dex save'. Always set this — an unlabelled roll request is a mystery. |
options.text |
string |
none | Chat template for the result. {result} is replaced with the rendered roll. |
options.pipeToChat |
boolean |
true |
false rolls silently on the owner's client — no chat row, no 3D dice. You still get the total. |
Returns
Promise<number | null>:
- a number — the total the owner rolled
null— they declined, or never answered
null is not 0. Handle it explicitly; treating a decline as a rolled zero
turns "I'd rather not" into a failed save.
const save = await target.requestRoll('1d20+dex', { label: 'Dex save' });
if (save === null) return;
What the other player sees
The request goes over the socket to the owner of the token it was called on. They get a prompt naming the token and the label, with a roll and a decline option. The roll executes on their client, with their 3D dice, against that token's stats — so a player still rolls their own saves.
The result lands in chat inside your action's frame, so the exchange reads as one event rather than two unrelated messages.
Examples
A saving throw
const dc = 15;
const damage = roll('8d6', { label: 'Fireball' });
const save = await target.requestRoll('1d20+dex', { label: 'Dex save' });
if (save === null) return;
target.applyDamage(save >= dc ? Math.floor(damage / 2) : damage, 'fire');
Every target saves in turn
for (const t of targets) {
const save = await t.requestRoll('1d20+wis', { label: `Wis save (DC ${dc})` });
if (save === null) continue;
if (save < dc) t.applyDamage(roll('4d6'), 'psychic');
}
An opposed check
const mine = roll('1d20+athletics', { label: 'Grapple' });
const theirs = await target.requestRoll('1d20+athletics', { label: 'Resist grapple' });
if (theirs === null) return;
sendMessage(theirs >= mine
? `${target.name} breaks free.`
: `${token.name} has ${target.name} pinned.`);
Custom result text
await target.requestRoll('1d20+con', {
label: 'Con save',
text: `${target.name} resists the poison with {result}.`,
});
Notes and gotchas
- Your action stops until they answer. Everything after the
awaitwaits on another human. Do the parts that do not depend on the answer first. - A loop over targets is sequential. Each
awaitwaits for that player before the next prompt goes out. For a big area effect, consider whether the GM would rather roll them. - Expressions resolve against the rolling token.
'1d20+dex'uses the target's dexterity — which is the whole point, but it means a property that exists on the caster and not the target quietly drops out of the total. - The token needs an owner. A token nobody owns has nobody to prompt, and
the request resolves
null. - The Test panel really sends it. Unlike chat messages, roll requests reach the other player during a test run.
- Unhandled
nullis the most common bug in action scripts. Check it every time.
See also
roll()— rolling for the acting tokenapplyDamage()— what usually follows a failed saveprompt()— asking the clicking user something