token.applyDamage()
Propose damage or healing. The change appears in chat as a card someone approves before any hit points move.
Part of the Action API · Function reference
Available on token, target, and every entry of targets.
Signature
token.applyDamage(amount: number, damageType?: string): void
Synchronous, returns nothing. The effect is a message in chat, not a change to the token.
Parameters
| Parameter | Type | Description |
|---|---|---|
amount |
number |
Positive damages, negative heals. Non-numeric values become 0. |
damageType |
string |
Optional. Which damage type this is — an id or name from damageTypes. Drives resistance and vulnerability. Omit for typeless damage. |
As a convenience, a single object argument is also accepted, so the result of a
prompt() form can be passed straight through:
const answer = await prompt('<label>Damage <input name="amount" type="number"></label>');
target.applyDamage(answer); // reads answer.amount and answer.damageType
Returns
Nothing.
There is no way to learn from script whether the damage was approved, modified, or denied — approval happens after your action has finished. An action cannot branch on the outcome.
Damage is proposed, not applied
applyDamage() never touches hit points directly. It calculates the result and
emits a card into chat showing:
- the raw amount and the damage type
- every resistance, vulnerability, and immunity that applied, itemised
- the final amount
- current HP, and what it would become
The GM or the token's owner then approves, modifies, or denies it. Hit points change on approval, and the combat log records it then.
This is deliberate. An action can propose damage; it cannot silently rewrite someone else's character. There is no flag, option, or trick that bypasses it from a script — the auto-approving path exists only for the GM's own heal/harm hotkey.
Damage
With a positive amount, the target's damage modifiers are applied first. Those
come from the token's damageModifiers, which is what makes a fire-immune
creature take nothing from your fireball without you writing any of that logic.
target.applyDamage(14, 'fire');
target.applyDamage(roll('2d6+3', { label: 'Greataxe' }), 'slashing');
The projected new HP is floored at zero.
Healing
A negative amount heals. Damage modifiers are skipped — nobody is resistant to being healed — and the card is presented as healing.
token.applyDamage(-5); // heal 5
token.applyDamage(-roll('2d4+2', { label: 'Cure Wounds' }));
Healing is capped at the token's hp_max when it has one. A damageType on a
heal is ignored.
Examples
Attack against AC
const attack = roll('1d20+attackBonus', { label: 'Attack' });
if (!target) return 'No target.';
if (attack >= (target.getProperty('ac') ?? 10)) {
target.applyDamage(roll('1d8+3', { label: 'Damage' }), 'slashing');
} else {
sendMessage(`${token.name} misses ${target.name}.`);
}
One roll, many targets
const damage = roll('8d6', { label: 'Fireball' });
for (const t of targets) {
t.applyDamage(damage, 'fire');
}
Half damage on a successful save
const dc = 15;
const damage = roll('8d6', { label: 'Fireball' });
for (const t of targets) {
const save = await t.requestRoll('1d20+dex', { label: 'Dex save' });
if (save === null) continue; // they declined
t.applyDamage(save >= dc ? Math.floor(damage / 2) : damage, 'fire');
}
Ask how much, then apply it
const answer = await prompt(`
<label>Damage <input name="amount" type="number" data-autofocus></label>
<label>Type <select name="damageType">
${damageTypes.map((d) => `<option value="${d.id}">${d.name}</option>`).join('')}
</select></label>
`);
if (answer === null) return;
target.applyDamage(answer.amount, answer.damageType);
Notes and gotchas
- One card per call. Ten targets means ten cards to approve. That is intentional — each is a separate decision — but consider whether your action really needs to hit ten things.
- Roll once, apply many. Calling
roll()inside the loop rolls separately for each target. Roll before the loop unless you want that. - Nothing is guaranteed to happen. The card can be denied. Do not write an action that assumes the HP moved.
- Type names must match your campaign's damage types for resistances to
apply. Read the
damageTypesarray rather than hard-coding strings. - In the Test panel the card is captured, not sent, so nothing can be approved and no HP changes.
See also
requestRoll()— saving throws before damagesetProperty()— direct writes, no approval, acting token onlyprompt()— asking for an amount- Chat and the combat log