Skip to content
3 Death Saves
Wiki updated as of 0.3.0 (Sep 2026)

Developers

The API is on the global GLYPH, and the same object is game.modules.get('glyph').api. It is set once Foundry reaches its ready phase. Wait for the glyph.ready hook rather than reading GLYPH from your own module’s ready hook, since hook order between modules isn’t guaranteed.

Hooks.once('glyph.ready', (api) => {
// api === GLYPH
});

Runs a glyph.trigger RegionBehavior’s handler on demand, the same way a Tile HUD button or an @Trigger link does.

ParameterTypeDescription
behaviorUuidstringUUID of the RegionBehavior to run.
handlerstringThe handler key to run. Defaults to "manual".
dataobjectEvent payload the handler reads as top-level {{path}} values. Defaults to {}.
await GLYPH.runTrigger('Scene.<sceneId>.Region.<regionId>.RegionBehavior.<behaviorId>', 'manual', { token: someTokenDocument });

The run always happens on the Primary GM’s client. Calling runTrigger from any other client, including a player’s, relays the request there through a query; the returned promise resolves once that run finishes. With no primary GM connected, a relayed call does nothing.

If the behavior has no handler under the given key, the run falls back to its Manual handler, then to its only configured handler if it has exactly one. Otherwise nothing runs. runTrigger throws when behaviorUuid doesn’t resolve to a glyph.trigger RegionBehavior.

data.token, if present, must be a TokenDocument. Every other field in data is passed through as-is and must be JSON-safe.

Gates still apply to a runTrigger call: restrictions, cooldown, Once Per Token, and the rest described on Trigger Configuration.

Returns whether a module is installed and enabled.

if (GLYPH.isModuleActive('tenacity')) {
/* ... */
}

Returns text with every {{path}} placeholder filled from a run’s context, the same way built-in actions fill their fields.

Fills placeholders in text, then evaluates it as a dice formula. Resolves to a number, or null when text is empty.

registerAction(moduleId, name, definition)

Section titled “registerAction(moduleId, name, definition)”

Registers a custom action node, namespaced as ${moduleId}.${name}, so it appears in the Program tab’s node picker alongside the built-in actions. Call it once glyph.ready has fired. moduleId and name are both required.

Hooks.once('glyph.ready', (api) => {
api.registerAction('my-module', 'praiseToken', {
category: 'messaging',
label: 'Praise Token',
hint: "Posts a chat message naming the trigger's token.",
fields: [{ name: 'message', widget: 'text', label: 'Message', required: true }],
validate(node) {
if (typeof node.message !== 'string' || !node.message) throw new Error('message is required.');
},
async execute(node, context) {
await ChatMessage.create({ content: api.interpolate(node.message, context) });
}
});
});
KeyDescription
labelThe name shown in the node picker and on the node’s row in the tree. A localization key or a plain string.
hintShort description shown under the name in the node picker. Same rule as label.
categoryGroups the action under a heading in the node picker. See below.
fieldsThe node’s configurable fields, each { name, widget, label, hint?, required?, ...widget options }. Drives the form the Program tab renders for this node.
slotsNamed child-node arrays, for an action that runs its own nested actions the way If or For Each do. Most actions have none.
validate(node)Throws when the node’s configured values are invalid. Runs before the tree is saved.
execute(node, context)Runs the node once.
batchExecute(pairs)Optional. Replaces running execute once per item inside a For Each with a single coalesced pass over every {node, context} pair, when the loop body is only this one node.

A batchExecute can be as simple as running execute for each pair concurrently:

async batchExecute(pairs) {
await Promise.all(pairs.map(({ node, context }) => this.execute(node, context)));
}
WidgetValue
textA single-line string. Default when widget is omitted.
textareaA multi-line string.
numberA number. min, max, and step constrain it.
formulaA string, resolved later as a dice formula or left as plain text.
booleanA checkbox.
selectOne choice from a choices map of {value: label}.
multiSelectZero or more choices from a choices map.
referenceA {kind, value} object pointing at a document, using the same reference kinds as the built-in actions. documentType restricts what it can point at.
pointA {x, y, elevation} object, or a reference-shaped object when pointing at a document’s location.
uuidA document UUID string. documentType restricts what it can point at.
expressionA condition expression string, in the syntax covered on Placeholders and Conditions.
jsonA JSON-encoded string.
fileA file path, opened through Foundry’s file picker. filePickerType selects the picker mode.
tagRefA landing tag, offered from the tags already used in the current tree.
handlerRefOne of the behavior’s own handler names.
resolverSelectA collection id, offered from the registered collection resolvers and the run’s current variables.
rollModeOne of Foundry’s chat roll modes.
languageOne of the active system’s configured languages, when the system exposes any.
statusEffectOne of the active system’s status effect ids.
systemAbility, systemSkill, systemDamageTypeA choice from the active system’s ability, skill, or damage type configuration, when the system exposes one. Falls back to a plain text field otherwise.
fxmasterEffectOne of FXMaster’s particle effect keys.
journalAnchorA page, or a page heading, on the journal entry referenced by the node’s own uuid field.
customWhatever field.render(value, path, ui) returns, as raw HTML.

node is the plain object holding the field values a GM configured, keyed by each field’s name. Values arrive exactly as configured, with any {{path}} text unfilled. Pass them through interpolate or resolveNumber to fill placeholders.

context is the active run’s state:

  • context.info - frozen facts about the run: id, region, scene, event ({name, data, user}), behavior, isAuthority, triggerCount.
  • context.variables - the trigger’s persisted variables, by name.
  • context.previous - the most recent producing action’s result.
  • context.results - every producing action’s result so far, merged by name.
  • context.item - the current item, when the node runs inside a For Each body.

A node that produces a value for later actions to read sets context.previous to it directly, and merges named values into context.results: assign context.results[bucket] = value for one named result, or Object.assign(context.results, value) when value is itself a plain object of named values.

The node picker’s categories: audio, flow, fxmaster, messaging, scene, structural, tagger, tile, token, variables. flow and structural are used by Glyph’s own program-flow nodes. A custom action can use any of the others, or its own id to group under a heading of its own. An id with no matching GLYPH.CATEGORIES.<id> localization key shows that raw key as the picker heading, so localize it in your own module’s language file, or reuse an existing id.

HookArgumentsFires
glyph.ready(api)Once, during Foundry’s ready phase, once the API is mounted.
glyph.preTriggerAction(behavior, event)On the primary GM’s client, after every other gate has passed, immediately before History and the cooldown clock are written.
glyph.triggerAction(behavior, event)On the primary GM’s client, after a run finishes successfully.

event is {name, data, user}: the event name, its data payload, and the triggering User.

glyph.preTriggerAction fires with Hooks.call, so a listener returning false cancels the run and stops any later glyph.preTriggerAction listener from running. A cancelled run doesn’t start the cooldown clock or write to History, but the Minimum Required attempt counter has already incremented by that point.

A glyph.trigger RegionBehavior can be created directly, without the configuration sheet, when the handler is simple:

await region.createEmbeddedDocuments('RegionBehavior', [
{
type: 'glyph.trigger',
name: 'Say hello',
system: {
events: [CONST.REGION_EVENTS.TOKEN_ENTER],
handlers: {
[CONST.REGION_EVENTS.TOKEN_ENTER]: { type: 'sequence', children: [{ type: 'chatMessage', text: 'Hello, {{token.name}}!' }] }
}
}
}
]);

Every other system field, such as userRestriction or cooldown, defaults the same way a freshly added trigger does on the General tab.