Skip to main content

API

Splash exposes an API for module and macro authors. Reach it at:

const splash = game.modules.get('splash').api;

The API is assigned during Foundry's init hook, and the splash.init hook fires immediately after. Register custom types from a listener on splash.init so they exist before any splash is built.

Hooks.once('splash.init', () => {
const api = game.modules.get('splash').api;
// register your sprites, actions, effects, animations, triggers here
});

Every first-party sprite, action, effect, animation, and trigger registers through this same surface, so the registration shape below is exactly what the built-ins use.

Registering types

Each register call takes a type string (the discriminator stored on the object's data), a human name shown in the editor's pickers, and a builder.

Sprites

api.registerSprite('my-sprite', 'My Sprite', (sprite, state, context) => {
// return a PIXI.DisplayObject (or a Promise of one)
});
ArgumentWhat it is
spriteThe sprite's data, including your custom fields.
stateThe state the sprite is being instantiated into.
contextPer-sprite wiring back into the runtime. context.onAction(action) runs an action through the owning splash instance; call it from a click handler to keep behavior instance-scoped.

Actions

api.registerAction('my-action', 'My Action', (action) => {
// run a side effect
});

The processor receives the action's data. Actions registered this way run through the runtime's external executor, which is how the built-in macro action fires a Foundry macro. The value and state actions (set-value, increment-value, change-state, close) are handled inside each running splash and are registered only so the editor can list them.

Effects

api.registerEffect('my-effect', 'My Effect', (app, effect) => {
// return a PIXI.Filter (or a Promise of one)
});

The builder gets the PIXI Application and the effect's data, and returns a filter applied to the sprite.

Animations

api.registerAnimation('my-anim', 'My Animation', (animation, sprite, app) => {
// mutate sprite.filters or tween; runs for the animation's duration
});

The builder gets the animation's data, the target PIXI.DisplayObject, and the PIXI Application. Use animation.duration and animation.delay to schedule cleanup, the way the built-in dissolve and glitch animations do.

Triggers

A trigger is a source in the world (a region, a door) wired to launch a splash. Register one with a type, a label, and the rest of the trigger definition.

api.registerTrigger('my-trigger', 'My Trigger', {
icon: 'fa-solid fa-bolt',
createBinding: async (splashUuid) => {
// interactively wire a new binding; resolve true on success
return true;
},
listBindings: () => [
// every live binding of this type, world-wide
],
removeBinding: async (binding) => {
// tear the binding down
},
});

A TriggerBinding carries an id (stable, used for removal), type, splashUuid, a human summary for the triggers list, and an optional sceneId. See Triggers for how bindings surface in the UI.

Inspecting the registry

GetterReturns
api.registeredAnimations{ type, name }[]
api.registeredEffects{ type, name }[]
api.registeredActions{ type, name }[]
api.registeredTriggersthe registered TriggerDefinition[]
api.getTrigger(type)one trigger definition, or undefined
api.bindingsForSplash(uuid)every live binding pointing at that splash

Launching splashes

These methods take a splash journal page UUID and respect view and trigger permissions.

MethodWhat it does
api.launch(uuid, opts)Open the splash using its stored presentation. A handout opens a windowed app; scene, hud, and full open a fullscreen overlay.
api.show(uuid, opts)Show the splash at a given layer.
api.openHandout(uuid)Open the handout window directly.
api.close(opts)Close the active splash locally.

launch and show accept { global, targetUser }. global is GM-only and shows the splash table-wide, persisting until closed. targetUser shows it transiently to one player. With neither, the splash opens locally for you. show also takes layer (full by default). close accepts { global }, GM-only, to kill the splash for the whole table.

await api.launch('JournalEntry.abc.JournalEntryPage.def', { global: true });

See Layers & Modes for what each presentation looks like, and Synced Mode for how global splashes share state.

Inline-macro scripts

A button's script action runs a snippet of JavaScript inside the running splash. The source executes as an async function body with three arguments in scope.

NameWhat it is
scopeThe firing sprite's node in the splash's sprite tree. The node carries id, name, type, parent, children, and child.get(name) to find one of its children by name (reach a sibling through scope.parent.child.get(name)). scope.get(property) and scope.set(property, value) read and write a sprite property, and scope.text is a live accessor for its text. Writes are ephemeral synced overrides, not saved to the document.
contextThe firing sprite's data bag (scope.context), a plain object you can stash values on.
apiA small surface for driving the splash from the script (below).

The script api is scoped to this splash instance, not the module-level API:

MemberWhat it does
api.changeState(load, unload)Load and unload states by id, the same as a change-state action.
api.setValue(key, value)Set a named runtime value.
api.close()Request the splash close.
api.unlockDoor(uuid)Unlock a door. Defaults to the door that triggered the splash; routed through the GM when a player can't write walls.
api.triggerWhat launched this splash, e.g. { door } for a door trigger.
// Reveal a hidden state when the dialed letters spell the word.
if (scope.child.get('lock').text === 'OPEN') {
api.changeState(['vault-open'], ['vault-locked']);
api.unlockDoor();
}

scope.set and scope.text write synced overrides, so a dialed letter or revealed value stays consistent across the table in synced mode. See Actions & Interactivity for wiring buttons to scripts.