Skip to content

API - Effects & Bonuses

Back to API Reference


Apply & Remove

applyEffectsToTokens async → Array<Token>


await api.applyEffectsToTokens(options, extraOptions)

options Object:

Param Type Default Description
inside options
tokens Array<Token> required Targets
effectNames string\|{ name?: string; icon?: string; isCustom?: boolean }\|Array required "prone" or { name, icon, isCustom }. isCustom: true marks a Temporary-Custom-Statuses effect, not a built-in status
note string "" Flavor note
duration Object {} { label, turns, rounds, overrideTurnOriginId } - label is a duration label. When overrideTurnOriginId is set, duration ticks down from that token's turn instead of the target's
checkEffectCallback (token: Token, effectData: object) => boolean null Dup-check predicate (token, effectData) => boolean. Returning true blocks the apply with a warning
notify Object\|boolean true Notification config { prefixText, source, whisper } (or true)
refresh boolean false If the effect already exists, reset its duration instead of blocking. Stack untouched

extraOptions Object: { stack?: number, linkedBonusId?: string, consumption?: object, statDirect?: object, changes?: Array, ...customFlags }

Those extra keys are also the effect's identity on apply. An existing effect of the same name only stacks when every one of them matches. A mismatch is treated as a distinct effect and a new one is created.

await api.applyEffectsToTokens(
    { tokens: hitTokens, effectNames: ['lockon'] },
    { stack: 2 }
);
applyMark async → Promise<Token[]>
findMarkedTokens → Token[]
clearMarks async → Promise<Token[]>


await api.applyMark(sourceToken, targets, { effect, note, duration, flagKey, extraOptions })
api.findMarkedTokens(sourceToken, effectName, { flagKey })   // → Token[]
await api.clearMarks(sourceToken, effectName, { flagKey })   // → Token[] cleared

Params: targets Token[] · effectName string the mark's effect name

Source-stamped effect lifecycle: applyMark applies the effect with { [flagKey]: sourceToken.id } stamped on it, findMarkedTokens scans the scene for tokens carrying it, clearMarks sweeps them all. The Suppress / Engineer's Mark / Sniper's Mark pattern.

See findEffectFrom and findEffectsOnToken for the lookup side.

Param Type Default Description
inside options
effect string\|{ name, icon, isCustom } required What to apply
note / duration string / Object "" / indefinite Forwarded to applyEffectsToTokens
flagKey string 'markSourceId' Stamp key. Pass a feature-specific one to keep marks independent
extraOptions Object {} Extra flags stamped alongside
await api.applyMark(reactorToken, [target], { effect: 'Suppress', flagKey: 'suppressSourceId' });
const marked = api.findMarkedTokens(reactorToken, 'Suppress', { flagKey: 'suppressSourceId' });
await api.clearMarks(reactorToken, 'Suppress', { flagKey: 'suppressSourceId' });
removeEffectsByName async → void


await api.removeEffectsByName(targetID, effectName, originID, extraFlags)

Single-token version of removeEffectsByNameFromTokens.

Param Type Default Description
targetID string required The token ID to remove effects from
effectName string\|{ name: string } required Effect name to match and remove
originID string null Only remove effects whose stored originID flag matches
extraFlags Object null Key/value pairs that must ALL match the effect's flags['lancer-automations'] data
await api.removeEffectsByName(target.id, 'Suppress', reactorToken.id);
removeEffectsByNameFromTokens async → Array<Token|TokenDocument>


await api.removeEffectsByNameFromTokens(options)

Use deleteEffect instead for one specific effect by ID.

Param Type Default Description
inside options
tokens Array<Token\|TokenDocument> required Tokens to remove from
effectNames string\|{ name?: string; icon?: string; isCustom?: boolean }\|Array required Effect name(s) to match and remove
originId string null Only remove effects whose stored originID flag matches this value
extraFlags Object null Key/value pairs that must ALL match the effect's flags['lancer-automations'] data
notify Object\|boolean true Notification config

originId and extraFlags are independent filters, both applied when provided.

Example:

await api.removeEffectsByNameFromTokens({
    tokens: [targetToken],
    effectNames: ["Suppress", "impaired"],
    extraFlags: { suppressSourceId: reactorToken.id }
});

deleteEffect async → Promise<void>


await api.deleteEffect(token, effect)

Deletes one active effect by object or ID, no name matching. Routes through the GM socket automatically for non-GM users.

Param Type Description
token Token\|TokenDocument\|string The token (or its ID) that owns the effect
effect ActiveEffect\|string The effect (or its ID) to delete

Example:

const effects = api.getAllEffects(target);
await api.deleteEffect(target, effects[0]);

deleteAllEffects async → Promise<void>
executeEffectManager async → Promise<void>


await api.deleteAllEffects(tokens)     // Removes ALL active effects from the provided tokens
await api.executeEffectManager(options) // Opens the Effect Manager UI
Param Type Description
tokens Array<Token\|TokenDocument> Tokens to clear (deleteAllEffects)

executeEffectManager(options) - options: { item?, actor?, forcePrototype?, initialTab? }. The first three pre-select the target (an item's prototype, an actor's active token, or the actor prototype when forcePrototype). initialTab opens on a named tab: 'standard', 'custom', 'bonus', 'manage'.

await api.deleteAllEffects([token]);
untilEndOfTurn
untilStartOfTurn → object
currentTurnKey → string|null


api.untilEndOfTurn(token, turns = 1)      // → duration object
api.untilStartOfTurn(token, turns = 1)
api.currentTurnKey()                       // → "round:turn", null out of combat

untilEndOfTurn / untilStartOfTurn build the "until the end of their next turn" duration: the token becomes the turn origin, and the count is bumped by one when it is already that token's turn. Use these instead of hand-writing { label: 'end', turns: 1, rounds: 0 }, which is off by one if applied on the target's own turn.

currentTurnKey stamps "the turn this happened on", so a later turn-end handler can tell whether it is looking at the same turn.

await api.applyEffectsToTokens({ tokens: [target], effectNames: ['slow'], duration: api.untilEndOfTurn(target) });

Find & Query

findEffectOnToken → ActiveEffect | undefined


api.findEffectOnToken(token, identifier)

First match by name or predicate. The string form uses the house name rules (exact name, custom-status originalName, effect flags, loose includes, status id) and delegates to findEffectsOnToken, which is the one to use for flag filters or all matches.

Param Type Description
token Token\|TokenDocument The token to search
identifier string\|((e: ActiveEffect) => boolean) Effect name (string) or predicate (effect) => boolean

Example:

const cover = api.findEffectOnToken(target, "Soft Cover");
const stacked = api.findEffectOnToken(target, e => (e.flags?.statuscounter?.value ?? 0) > 1);

findEffectsOnToken → any[]


api.findEffectsOnToken(token, effectName, options)

Every matching effect on one token, same loose name rules as findEffectOnToken. Use it for flag filters or all matches rather than the first.

Param Type Default Description
token Token\|TokenDocument required The token to search
effectName string required Name or status id
inside options
extraFlags Object {} Module flags that must match exactly
hasFlags string[] [] Flag keys that must be present, any value
excludeId string null Effect id to skip, for onStatusRemoved "is any other left?" checks
const others = api.findEffectsOnToken(token, 'Overshield', { excludeId: effect.id });
findEffectFrom → ActiveEffect | undefined


api.findEffectFrom(token, effectName, sourceToken)

The originID variant: the effect on token that sourceToken applied, matched on the origin stamp left by addGlobalBonus / applyEffectsToTokens. Use findEffectsOnToken with extraFlags instead when the source was stamped by applyMark's flagKey.

The name match here is exact. The loose house name rules do not apply.

Param Type Description
token Token The token carrying the effect
effectName string Exact effect name
sourceToken Token The token that applied it
const eff = api.findEffectFrom(target, 'Lock On', reactorToken);
hasStatus → boolean


api.hasStatus(tokenOrActor, ...statusIds)

True when any of the given status ids is active.

Param Type Description
tokenOrActor Token\|TokenDocument\|Actor Whose statuses to read
statusIds ...(string\|string[]) Status ids, or arrays of them. Matches if any is present

Takes status ids ('cover_hard'), not display names. For effects by name, or for flag filters, use findEffectOnToken.

if (api.hasStatus(target, 'prone', 'cover_hard', 'cover_soft'))
    return;
inDangerZone → boolean


api.inDangerZone(tokenOrActor)

Params: tokenOrActor Token|Actor

True when heat is at or above half the heat cap. Same rule the onHeatGain trigger reports as inDangerZone.

const difficulty = api.inDangerZone(target) ? 1 : 0;
getAllEffects → Array<ActiveEffect>


api.getAllEffects(target)

Returns all active effects on the target, including unflagged player-added ones.

Param Type Description
target Token\|TokenDocument\|Actor The target to inspect
const names = api.getAllEffects(token).map(e => e.name);

Charges & Immunity

An effect's charges live in a single counter, flags.statuscounter.value. The stack extra option, a bonus's uses, and "charges" here all write and read that one number.

consumeEffectCharge async → boolean


await api.consumeEffectCharge(effect)

Decrements the counter by 1. If it reaches 0, the effect is deleted. Grouped effects (via consumption.groupId) share a counter and are all deleted together.

Param Type Description
effect ActiveEffect The effect to consume a charge from

Returns true if consumed, false for a null or unparented effect and for an effect with no consumption data. A non-GM who does not own the actor gets true as soon as the GM socket request is sent, before the GM has acted on it.

const eff = api.findEffectOnToken(token, 'Shield Charges');
if (eff) await api.consumeEffectCharge(eff);
triggerEffectImmunity async → void


await api.triggerEffectImmunity(token, effectNames, source, notify)

Removes the named effects from the token and announces immunity in chat.

Param Type Default Description
token Token\|TokenDocument required The immune token
effectNames string\|Array<string> required Effect name(s) to remove
source Item\|string "" Source of immunity (item or text)
notify boolean true Post chat notification
await api.triggerEffectImmunity(target, ['impaired'], reactorToken, true);
checkEffectImmunities → Array<string>


api.checkEffectImmunities(actor, effectIdOrName, effect, state, tokens)
api.getEffectImmunityBonuses(actor, effectIdOrName, effect, state, tokens)

Returns an array of source names (e.g. ["Immunity Bonus", "Armor Plating"]), empty when the actor is not immune. Test .length: the empty array is truthy. getEffectImmunityBonuses returns the bonus objects instead.

Param Type Default Description
actor Actor required The actor to check
effectIdOrName string required Effect ID or name to check immunity for
effect ActiveEffect null Optional effect object for additional context
state Object null Optional flow state
tokens Object {} { ownerTokenId, otherToken }, the bearer and whoever applied the status

Called bare it returns the unfiltered list. Most status applications have no applier, a token HUD click included, so applyToCondition is often skipped here.

if (api.checkEffectImmunities(target.actor, 'prone').length) return;

Global & Constant Bonuses

addGlobalBonus async → string (Bonus ID)


const bonusId = await api.addGlobalBonus(actor, bonusData, options)

Returns undefined if actor is falsy.

options Object:

Param Type Default Description
duration string 'end' in combat A duration label. A string here, unlike the effect APIs where duration is an object
durationTurns number 1 Origin turns until the effect ends. See below
origin Token\|TokenDocument\|string the actor's token Whose turns durationTurns counts
consumption ConsumptionConfig null A Consumption config
refresh boolean false Re-add an existing bonus id in place: values replaced, linked effect's duration reset instead of blocked
icon string per bonus type Image path for the linked effect, overriding the type's default icon
forcePrototype boolean false Ignore the scene token and write the effect on the actor itself, so spawned tokens inherit it

durationTurns counts origin turns until the effect ends: - 0: next matching trigger. If applied during origin's own turn with duration: "end", ends at end of that same turn. With duration: "start", it ends at start of the next turn. Off the origin's turn, 0 clamps to 1. - 1: one full origin turn (default). With duration: "end" applied during origin's own turn, ends at end of origin's next turn. - n ≥ 2: n origin turns.

None of that applies out of combat, or with duration: 'indefinite'. There is no clamping: the effect is created indefinite, with no turn count. The prototype write (no scene token, or forcePrototype) is indefinite too.

bonusData Object:

Core fields (all bonus types)
Property Type Description
id string Optional custom ID
name string Display name
type string "accuracy", "difficulty", "damage", "stat", "immunity", "tag", "range", "multi", "target_modifier", "reroll", "movement_extra"
val number\|string Value for stat, accuracy, difficulty, tag, or range bonuses
uses number Charges. Written to the linked effect's counter, the same one consumeEffectCharge reads
consumeOnUsage boolean Burn 1 charge only when the bonus actually applies (still checked at roll time / immunity blocked / reroll accepted). Supported: accuracy, difficulty, damage, target_modifier, reroll, immunity (effect/crit/hit/miss/damage/resistance/provoke/terrain). An immunity charge burns where that immunity is consulted, never on the bearer's own roll, so resistance burns at damage-apply time. Default true, except immunity which defaults false. The Auto-consume on: triggers burn regardless and take precedence.
frequency "round" \| "turn" \| "combat" Applies once per round / turn / combat, then stops until the window passes. Gated with the gate API under the key bonus:<id>, burned where the bonus is actually consulted. Out of combat gates never block, so the bonus always applies
rollTypes Array ["attack"], ["check"], etc.
condition string\|fn (state, actor, data, context) => boolean. Per-bonus gate - if false, the whole bonus is skipped. On an immunity, actor is the other party and the owner is context.ownerTokenId.
applyToCondition string\|fn (target, state, reactorToken, entry) => boolean. Per-target gate for accuracy, difficulty, target_modifier and every filtered immunity. reactorToken is the owner, target the Token rolled against. On an immunity target is the other party instead, and entry is null. Skipped, not failed, when there is no other party. Must be synchronous. Serialized via @@fn: - survives reloads.
itemLids Array LID filters
applyTo Array Token ID filters. Static - set at bonus creation. For a dynamic per-target gate, see applyToCondition.
applyToTargetter boolean Reverse direction: the bonus sits on the defender and applies to anyone rolling against it, never to the owner's own rolls. condition then runs in the attacker's flow (state.actor is the attacker, the owner is context.ownerTokenId) and applyTo filters attacker token IDs instead. Not for stat or range. How Brace grants its +1 difficulty.
tier 1\|2\|3 Gate to an NPC owner tier. Unset = any. Non-NPC owners ignore it
Immunity fields (type: "immunity")
Property Type Description
subtype string One of the immunity subtypes
effects Array Only for subtype: "effect". List of effect/status names (e.g. ["Prone", "Immobilized"])
damageTypes Array Only for subtype: "damage" or "resistance". List of damage types (e.g. ["Energy", "Kinetic"]). "all" covers every type, and "variable" is treated the same way. Infection is included only while its integration setting is on

"provoke" acts like permanent DISENGAGE. No extra fields required.

Filters honoured per subtype:

Subtype Honoured
damage, resistance, crit, hit, miss rollTypes, itemLids, condition, applyToCondition
effect, provoke condition, applyToCondition
terrain, obstacle, elevation none

itemLids filters the other party's weapon. applyTo and applyToTargetter are ignored. A filtered resistance is not written to system.resistances, so it no longer shows as a checked resistance on the sheet.

Movement extra fields (type: "movement_extra")
Property Type Description
subtype string "boost" (default) lengthens every Boost, "standard" lengthens the Standard Move
val number Spaces added to each matching move

Feeds both the ruler bands and the movement cap through getMovementBands. Use it for standing effects ("your boosts are longer"). For a one-shot on a single move use recordMovementExtra instead. The legacy type id speed_boost_extra is read as movement_extra with subtype: "boost".

Target modifier fields (type: "target_modifier")
Property Type Description
subtype string Attack: "invisible", "no_invisible", "no_cover", "soft_cover", "hard_cover". Damage: "ap", "half_damage", "paracausal", "crit", "hit", "miss"
applyToCondition string\|fn Per-target gate, see the core fields. Also runs on the damage card and on each toggle.

"no_invisible" forces plugins.invisibility.data = 0 on the target, bypassing "invisible".

Example - ignore invisibility only within range 3:

await api.addConstantBonus(actor, {
    id: 'lesser-sight',
    name: 'Lesser Sight',
    type: 'target_modifier',
    subtype: 'no_invisible',
    applyToCondition: (target, state, reactorToken) => {
        const api = game.modules.get('lancer-automations')?.api;
        return api?.getTokenDistance(reactorToken, target) <= 3
            && target?.actor?.effects?.some(e => e.statuses?.has('invisible'));
    }
});

Tag fields (type: "tag")
Property Type Description
tagName string Name of the custom tag (e.g. "Inaccurate")
tagMode string "add" or "override"
removeTag boolean If true, negates the tag instead of adding it
Range fields (type: "range")
Property Type Description
rangeType string "Range", "Threat", "Line", "Blast", "Burst", "Cone"
rangeMode string "add" (default, accepts negative val), "override" (set existing or create), or "change" (replace all ranges with a single entry)
Reroll fields (type: "reroll")
Property Type Description
subtype string "retry" (default), "highest", "lowest", or "choose". See resolution table below.
rollTypes Array<string> "attackRoll", "techAttackRoll", "damageRoll", "skillRoll", "structureRoll", "stressRoll". Empty = all.

Offered via a choice card before onRoll fires. Consumed only on Use (Keep leaves the charge).

Subtype Resolution after the alt roll runs
"retry" Replace original with the alt (current default behavior).
"highest" Auto-keep max(originalTotal, altTotal). Stacking gives best-of-N+1.
"lowest" Auto-keep min(originalTotal, altTotal). Stacking gives worst-of-N+1.
"choose" Second card prompts Original (X) / Alt (Y) and the user picks.

Stacking: when an actor has multiple reroll bonuses matching a roll, they fire sequentially, each operating on the current total. Candidates are sorted by subtype priority (retry → highest/lowest → choose). Damage rolls deep-snapshot damage_results/reliable_results/targets, so "keep original" restores the full breakdown.

Multi / Damage fields
Property Type Description
bonuses Array Only for type: "multi". Array of sub-bonus objects.
damage Array Damage bonus. Shape depends on damageMode: [{ type, val }] for add/add_base/replace, [{ from, to }] for change_type (use from: "all" as a fallback catch-all, and specific from types win over "all").
damageMode string "add" (default, adds bonus damage rows in the Bonus Damage section), "add_base" (appends extra damage rows to the weapon's Base Damage), "replace" (weapon's base damage is fully replaced), "change_type" (weapon's damage values kept, types remapped). All modes except add are actor-wide only. applyTo is stripped on save for those.
stat string Property path (e.g. system.hp.max)
statMode string "add" (default, adds val to the current stat) or "replace" (sets the stat to val). Reversal preserves any damage / healing taken during the effect (delta-based, see statDirect).
removeGlobalBonus async → boolean


await api.removeGlobalBonus(actor, bonusIdOrPredicate, skipEffectRemoval)

Removes one or more global bonuses from an actor. Also deletes linked active effects unless skipEffectRemoval is true.

Param Type Default Description
actor Actor required The actor to modify
bonusIdOrPredicate string\|((bonus) => boolean) required Bonus ID string, or predicate (bonus) => boolean to match multiple
skipEffectRemoval boolean false If true, keeps the linked active effects

Example:

await api.removeGlobalBonus(actor, "defense-net-abc123");

await api.removeGlobalBonus(token.actor, b => b.context?.ownerTokenId === reactorToken.id);

getGlobalBonuses → any[]
getGlobalBonus → object|null


const all    = api.getGlobalBonuses(actor)        // → Array<BonusData> (empty if falsy)
const single = api.getGlobalBonus(actor, bonusId)  // → BonusData | null
Param Type Description
actor Actor The actor to inspect
bonusId string The bonus ID (for getGlobalBonus only)

getGlobalBonuses drops bonuses gated to a tier the NPC owner is not. getGlobalBonus does not: a by-id lookup returns the bonus whatever its tier gate.

const bonus = api.getGlobalBonus(actor, 'lightning-reflexes');
addConstantBonus async → Promise<void>
getConstantBonuses → any[]
removeConstantBonus async → Promise<void>


await api.addConstantBonus(actor, bonusData)              // same bonusData shape as addGlobalBonus
const bonuses = api.getConstantBonuses(actor)              // → Array<BonusData> (empty if falsy)
await api.removeConstantBonus(actor, bonusIdOrPredicate)   // string ID or predicate

Constant bonuses are permanent (stored in flags, not linked to an active effect). Auto-generates an id if not provided. getConstantBonuses drops bonuses gated to a tier the NPC owner is not.

Param Type Description
target / actor Actor The actor to modify/inspect
bonusData Object Same shape as addGlobalBonus
bonusIdOrPredicate string\|((bonus) => boolean) Bonus ID or predicate (bonus) => boolean

When the target is an item or a prototype actor, use linkBonusToItem / linkBonusToActor instead.

await api.addConstantBonus(actor, { name: 'Core Power', type: 'accuracy', val: 1, rollTypes: ['attack'] });
await api.removeConstantBonus(actor, (b) => b.name === 'Core Power');

Attach to items and prototype actors

Statuses and bonuses can be attached directly to an item or a prototype actor instead of a token. The entry lives on the source doc, and applies to the token's actor on item-add, token-spawn, or re-enable. It's cleaned up on remove / destroy / disable. Charges persist across the cycle.

linkEffectToItem
linkEffectToActor
ensureLinkedEffect async → Array


await api.linkEffectToItem({ items, effectNames, note, duration }, extraOptions)
await api.linkEffectToActor({ actors, effectNames, note, duration }, extraOptions)
await api.ensureLinkedEffect({ items, effectNames, note, duration }, extraOptions)  // idempotent

Attaches a status to each source doc. Fires immediately on any active tokens.

Param Type Default Description
inside options
items / actors Array required Source docs
effectNames string\|Object\|Array required Same shape as applyEffectsToTokens
note string "" Flavor note
duration Object {} (permanent) { label, turns?, rounds? }

extraOptions keys are stored on the source and copied to every effect that comes from it. extraOptions.tier (1-3) gates materialization to NPC owners of that tier.

ensureLinkedEffect is linkEffectToItem that skips effects the item already carries as a template (match = effect name + every extraOptions flag). The onInit way to link: no hand-written guard needed.

await api.ensureLinkedEffect({ items: [item], effectNames: ['resistance_kinetic'] });
unlinkEffectFromItem
unlinkEffectFromActor async → Array


await api.unlinkEffectFromItem({ items, effectName, extraFlags })
await api.unlinkEffectFromActor({ actors, effectName, extraFlags })

Removes the entry from the source doc. Every effect that came from it is removed from active tokens too.

Param Type Default Description
inside options
items / actors Item[] / Actor[] [] Source docs to unlink from
effectName string "" Name of the linked effect
extraFlags Object null Must match the linked entry's flags
await api.unlinkEffectFromItem({ items: [item], effectName: 'resistance_kinetic' });
linkBonusToItem
linkBonusToActor
ensureLinkedBonus async → Array


await api.linkBonusToItem({ items, bonusData, addOptions }, extraOptions)
await api.linkBonusToActor({ actors, bonusData, addOptions }, extraOptions)
await api.ensureLinkedBonus({ items, bonusData, addOptions }, extraOptions)  // idempotent, needs bonusData.id

Attaches a bonus to each source doc. Applies immediately on active tokens. Each call returns one { item, templateId } (or { actor, templateId }) pair per source doc. Keep the templateId, it is what unlinkBonusFromItem / unlinkBonusFromActor take.

The duration in addOptions decides how it shows up:

  • 'constant': passive, invisible, no token icon (same as addConstantBonus).
  • anything else ('permanent', 'indefinite', 'end', 'start', 'round'): full bonus with icon, uses / consumption, and turn tracking (same as addGlobalBonus).

bonusData and addOptions shapes match addGlobalBonus.

ensureLinkedBonus is linkBonusToItem that skips items already carrying a template with the same bonusData.id. The onInit way to link a bonus.

await api.ensureLinkedBonus({ items: [item], bonusData: { id: 'smart-rounds', type: 'accuracy', val: 1, rollTypes: ['attack'] } });
unlinkBonusFromItem
unlinkBonusFromActor async → Array


await api.unlinkBonusFromItem({ items, templateId })
await api.unlinkBonusFromActor({ actors, templateId })

Removes the entry from the source doc. Every bonus that came from it is removed from active tokens too. Charge counts are saved back to the source first, so a re-link picks them back up.

Param Type Default Description
inside options
items / actors Item[] / Actor[] [] Source docs to unlink from
templateId string required The id of the link itself, from the link* call's return value

templateId is generated per link, not taken from bonusData.id. Passing bonusData.id matches nothing and the call is a silent no-op. When you no longer hold the return value, read it off the source with getLinkedBonuses.

const [{ templateId }] = await api.linkBonusToItem({ items: [item], bonusData: { id: 'smart-rounds', type: 'accuracy', val: 1 } });
await api.unlinkBonusFromItem({ items: [item], templateId });

const template = api.getLinkedBonuses(item).find(t => t.bonusData?.id === 'smart-rounds');
await api.unlinkBonusFromItem({ items: [item], templateId: template.id });
getLinkedEffects → any[]
getLinkedBonuses → any[]


api.getLinkedEffects(source)   // → ActiveEffect[]  status templates on Item or Actor
api.getLinkedBonuses(source)   // → Object[]        bonus templates on Item or Actor

Params: source Item|Actor

Returns the LINKED entries only, not merged with runtime state on the actor. A bonus entry is { id, bonusData, addOptions }, where id is the link's templateId and the bonus you passed in is under bonusData.

const hasTemplate = api.getLinkedBonuses(item).some(t => t.bonusData?.id === 'smart-rounds');
Manual apply / cleanup helpers all async


await api.applyItemTemplatesToTokens(item, tokens)         // apply the item's statuses to given tokens
await api.applyActorTemplatesToTokens(actor, tokens)       // apply the actor's statuses to given tokens
await api.applyItemBonusTemplatesToTokens(item, tokens)    // apply the item's bonuses
await api.applyActorBonusTemplatesToTokens(actor, tokens)  // apply the actor's bonuses
await api.cleanupItemBonusesFromActor(item, actor)          // remove bonuses that came from this item
await api.cleanupActorBonusesFromTokens(actor)              // remove bonuses that came from this actor

The lifecycle hooks call these for you. Only reach for them if you need to force a pass from custom code. All safe to call repeatedly - they skip anything already applied.

Args: item/actor source doc. The apply* helpers also take tokens (Array<Token|TokenDocument>). cleanupItemBonusesFromActor(item, actor) and cleanupActorBonusesFromTokens(actor).


Flow State Data Injection

During an active flow (attack, check, etc.), triggerData contains a flowState object. Inject ephemeral bonuses or share variables across triggers for the flow's lifespan.

flowState.injectBonus
flowState.injectFlowExtraData
flowState.getFlowExtraData


triggerData.flowState.injectBonus(bonus)            // add ephemeral bonus to current flow
triggerData.flowState.injectFlowExtraData(extraData) // merge into state.la_extraData
triggerData.flowState.getFlowExtraData()             // read la_extraData
  • injectBonus - ephemeral bonus (e.g. an accuracy bonus) applied to this flow's rolls, discarded when the flow completes.
  • injectFlowExtraData - merges into state.la_extraData, passing variables between trigger phases (e.g. onHit to onDamage).
  • getFlowExtraData - reads la_extraData back.
injectBonusToFlowState async → Promise<void>


await api.injectBonusToFlowState(state, bonus)

The standalone form of flowState.injectBonus, for when you hold the flow state rather than a triggerData. Pushes the bonus onto state.la_extraData.flow_bonus, generating an id if you didn't supply one. Does nothing if state is missing.

Param Type Description
state Object The flow state, e.g. triggerData.flowState
bonus Object Same shape as addGlobalBonus. Gets an id if absent
await api.injectBonusToFlowState(triggerData.flowState, {
    id: `lightning-reflexes-${reactorToken.id}`,
    name: 'Lightning Reflexes',
    type: 'immunity',
    subtype: 'hit'
});

Immunity Queries

getImmunityBonuses → any[]
checkDamageResistances → string[]
applyDamageImmunities → Array<{ type: string; val: any }>


api.getImmunityBonuses(actor, subtype?, state)              // → Array<object>
api.checkDamageResistances(actor, damageType, allowedIds)   // → Array<string>
api.applyDamageImmunities(actor, damages, state, bonuses)   // → Array<object>

Params: actor Actor | Token · subtype string immunity subtype, omit for all · damageType string · damages Array<{type, val}>

Function Description
getImmunityBonuses Returns the actor's immunity bonuses of the given subtype, or every immunity bonus when subtype is omitted. A Token resolves to its actor. Unknown subtypes log a console warning. No filters are applied, so this is the raw list.
checkDamageResistances Source names of the "resistance" subtype bonuses matching the damage type, not the bonus objects. Empty when there is no resistance. allowedIds (string[], default null) keeps only the filtered bonuses whose id is listed, which is how a decision made during the damage flow reaches apply time. null filters nothing.
applyDamageImmunities Takes an array of damage objects {type, val} and returns a copy with the immune types zeroed. Both val and amount are zeroed where present. With no immunities it returns the array it was given, not a copy. bonuses (object[], default null) supplies a prefiltered list instead of re-reading the flags.

getImmunityBonuses takes state only to fold in flow-scoped bonuses. To apply a bonus's own filters, use one of the three helpers below.

const resist = api.checkDamageResistances(target.actor, 'Energy');
if (resist.length) console.log(`Resisted by ${resist.join(', ')}`);
getApplicableImmunityBonuses
getAttackImmunityBonuses
getGateImmunityBonuses → any[]


api.getApplicableImmunityBonuses(actor, subtype, state, { flowType, ownerTokenId, otherToken })
api.getAttackImmunityBonuses(actor, subtype, attackerActor, state, { defenderToken, attackerToken })
api.getGateImmunityBonuses(actor, subtype, { ownerToken, otherToken, state })

getImmunityBonuses minus the bonuses their own filters reject.

Function Use
getApplicableImmunityBonuses Inside a flow. Runs every filter. flowType picks the tag set (default "damage").
getAttackImmunityBonuses crit, hit, miss. Adds flowType: "attack" and rebinds state.actor to the attacker.
getGateImmunityBonuses Outside a flow, for effect and provoke. Runs the two code fields only.

ownerToken is the bearer and becomes reactorToken. otherToken becomes applyToCondition's target.

Missing context skips a gate rather than failing it. With no context at all, the raw list comes back unfiltered.

const immune = api.getAttackImmunityBonuses(target.actor, 'crit', state.actor, state, {
    defenderToken: target,
    attackerToken: attacker
});
if (immune.length)
    await api.consumeImmunityUse(target.actor, 'crit', state, { bonuses: immune });
hasCritImmunity
hasHitImmunity
hasMissImmunity async → boolean


await api.hasCritImmunity(actor, attackerActor, state, tokens)
await api.hasHitImmunity(actor, attackerActor, state, tokens)
await api.hasMissImmunity(actor, attackerActor, state, tokens)

Returns true if the actor has any applicable immunity bonus of the corresponding subtype. Thin wrappers over getAttackImmunityBonuses, which returns the bonuses themselves when you need to burn the right charge.

Param Type Default Description
actor Actor required The actor to check
attackerActor Actor null The attacker. Without it nothing is filtered and any bonus counts
state Object null Optional flow state
tokens Object {} { defenderToken, attackerToken }. Needed for condition to reach reactorToken and for applyToCondition to run at all
if (await api.hasHitImmunity(target.actor, attackerActor, triggerData.flowState)) return;