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});runTrigger(behaviorUuid, handler, data)
Section titled “runTrigger(behaviorUuid, handler, data)”Runs a glyph.trigger RegionBehavior’s handler on demand, the same way a Tile HUD button or an @Trigger link does.
| Parameter | Type | Description |
|---|---|---|
behaviorUuid | string | UUID of the RegionBehavior to run. |
handler | string | The handler key to run. Defaults to "manual". |
data | object | Event 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.
isModuleActive(moduleId)
Section titled “isModuleActive(moduleId)”Returns whether a module is installed and enabled.
if (GLYPH.isModuleActive('tenacity')) { /* ... */}interpolate(text, context)
Section titled “interpolate(text, context)”Returns text with every {{path}} placeholder filled from a run’s context, the same way built-in actions fill their fields.
resolveNumber(text, context)
Section titled “resolveNumber(text, context)”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) }); } });});The definition
Section titled “The definition”| Key | Description |
|---|---|
label | The name shown in the node picker and on the node’s row in the tree. A localization key or a plain string. |
hint | Short description shown under the name in the node picker. Same rule as label. |
category | Groups the action under a heading in the node picker. See below. |
fields | The node’s configurable fields, each { name, widget, label, hint?, required?, ...widget options }. Drives the form the Program tab renders for this node. |
slots | Named 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)));}Fields
Section titled “Fields”| Widget | Value |
|---|---|
text | A single-line string. Default when widget is omitted. |
textarea | A multi-line string. |
number | A number. min, max, and step constrain it. |
formula | A string, resolved later as a dice formula or left as plain text. |
boolean | A checkbox. |
select | One choice from a choices map of {value: label}. |
multiSelect | Zero or more choices from a choices map. |
reference | A {kind, value} object pointing at a document, using the same reference kinds as the built-in actions. documentType restricts what it can point at. |
point | A {x, y, elevation} object, or a reference-shaped object when pointing at a document’s location. |
uuid | A document UUID string. documentType restricts what it can point at. |
expression | A condition expression string, in the syntax covered on Placeholders and Conditions. |
json | A JSON-encoded string. |
file | A file path, opened through Foundry’s file picker. filePickerType selects the picker mode. |
tagRef | A landing tag, offered from the tags already used in the current tree. |
handlerRef | One of the behavior’s own handler names. |
resolverSelect | A collection id, offered from the registered collection resolvers and the run’s current variables. |
rollMode | One of Foundry’s chat roll modes. |
language | One of the active system’s configured languages, when the system exposes any. |
statusEffect | One of the active system’s status effect ids. |
systemAbility, systemSkill, systemDamageType | A 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. |
fxmasterEffect | One of FXMaster’s particle effect keys. |
journalAnchor | A page, or a page heading, on the journal entry referenced by the node’s own uuid field. |
custom | Whatever field.render(value, path, ui) returns, as raw HTML. |
node and context
Section titled “node and context”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 aFor Eachbody.
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.
Categories
Section titled “Categories”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.
| Hook | Arguments | Fires |
|---|---|---|
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.
Creating a trigger from code
Section titled “Creating a trigger from code”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.