Skip to content

Events Reference

All event names and payload shapes. Listen via ctx.bus.on(name, fn). Emit via ctx.bus.emit(name, payload) (rarely needed — most mods only listen).

The TypeScript source of truth is the EventMap interface in mod-api.d.ts.

EventPayloadCancellable
map.loaded{ mapId, width, height }no
map.unloaded{ mapId }no
map.tile.changed{ mapId, layer, x, y, oldId, newId } (single-tile writes only)no
map.batch.changed{ mapId, layer, count, label }no
tool.activated{ toolId }no
selection.changed{ mapId, bounds, count }no
save.before{ mapId }yes
save.after{ mapId }no
save.failed{ mapId, error: string }no
mod.loaded{ id }no
mod.unloaded{ id }no
panel.opened{ panelId }no
panel.closed{ panelId }no
layer.changed{ mapId, layerIndex, change }no
viewport.changed{ mapId, x, y, zoom }no
hover.changed{ mapId, x: number|null, y: number|null }no
clipboard.changed{ hasData: boolean, tiles?: number }no
brush.changed{ size, rotation, flipH, flipV, opacity, hue, saturation, lighting }no
tileset.changed{ tilesetId, reason: "selected"|"edited"|"reloaded" }no
undo{ mapId, label }no
redo{ mapId, label }no
event.changed{ mapId, eventId, change: "created"|"deleted"|"moved"|"updated" }no
paste.before{ mapId, x, y, tileCount }yes
stats.changed{ global: GlobalStatsSnapshot, project: ProjectStatsSnapshot | null }no
keybind.changed{ actionId: string, oldKey: string, newKey: string }no
locale.changed{ locale: string }no
game.launch{ gameRoot: string }no
keybind.triggered{ actionId: string }no

When events fire

  • map.loaded — after a map’s tiles, layers, and shadow data have been loaded into memory and a tab opened. Safe time to query ctx.map.info(...).
  • map.unloaded — after a map tab is closed and removed from the editor. ctx.editor.activeMapId() returns null when the last map is closed.
  • map.tile.changed — fires once for each single-tile edit. For brush strokes that write many tiles, map.batch.changed fires once with the total count.
  • map.batch.changed — fires for every tile write batch (brush, fill, rectangle, paste, undo/redo). Use this when you want a coarse “the map changed” signal.
  • save.before — fires before the editor writes the map’s .rxdata file. Cancellable. Return { cancel: true, reason: "..." } to abort. Listeners are awaited in registration order; the first cancel wins.
  • save.after — fires after a successful write. A good place to generate companion artefacts (XML exports, manifests, etc.).
  • tool.activated — fires when the user (or a mod) switches the active tool, including switches to mod-registered tools.
  • mod.loaded / mod.unloaded — fires for any mod including yours. Useful when a mod depends on optional collaborators.
  • panel.opened / panel.closed — fires when a mod panel is opened or closed in the dock.
  • selection.changed — fires when the tile selection changes (set, clear, modify).
  • layer.changed — fires when a layer is mutated. change is one of: "visibility", "opacity", "added", "removed", "active".
  • viewport.changed — fires when the map viewport pans or zooms.
  • hover.changed — fires when the cursor moves to a different tile. x/y are null when the cursor leaves the map canvas.
  • clipboard.changed — fires after any copy, cut, or clipboard clear. tiles is the count of copied tiles (absent when cleared).
  • brush.changed — fires when the user changes any brush property (size, rotation, flip, opacity, hue, saturation, lighting).
  • tileset.changed — fires when the palette tileset is switched ("selected"), a tileset property is edited ("edited"), or tilesets are reloaded from disk ("reloaded").
  • undo / redo — fire after a successful undo or redo. label is the undo entry description.
  • save.failed — fires when a save operation throws an error. error is the error message string.
  • event.changed — fires after any RMXP event mutation (create, delete, move, update).
  • paste.before — fires before a paste operation is committed. Cancellable — return { cancel: true, reason: "..." } to abort. tileCount is the number of tiles in the clipboard.
  • stats.changed — fires periodically (~60s) with updated editor statistics snapshots. global contains lifetime stats, project contains current project stats (or null if no project open).
  • keybind.changed — fires when a keybind is changed via the settings dialog or the ctx.keybinds API. actionId is the affected action, oldKey and newKey are the normalized combo strings (e.g. "ctrl+s").
  • locale.changed — fires when the active editor language changes (View → Language, ctx.i18n.setLocale, or a mod-provided locale registering/unregistering). locale is the new locale code (e.g. "en", "es", or a mod-registered code). ctx.i18n.onChanged(cb) is the convenience wrapper.
  • game.launch — fires once per Run Game invocation (toolbar button, menu item, or the app.runGame shortcut), right after the game-root check passes — before dirty maps are saved and the game actually launches. Fires regardless of outcome (launched, already running, a Proton prefix prompt shown/cancelled on Linux, or a launch error), so it’s a reliable “the user asked to run the game” signal rather than a “game started” one.
  • keybind.triggered — fires when the global shortcut dispatcher resolves a keydown to a built-in actionId and is about to run its handler. This is the only reliable way for a mod to know “the keyboard was just used for X”: the dispatcher calls e.stopImmediatePropagation() right after resolving, and it’s mounted on window in the capture phase before any mod loads — so a mod’s own keydown listener, on whatever target or phase, never sees the event for a shortcut that actually fired. Does not cover mod-registered shortcuts (ctx.menu.registerMenuItem’s shortcut, ctx.ui.registerShortcut) — those resolve on a separate path.

Cancellable handlers

save.before and paste.before are the cancellable events. Listeners may return a Promise — the editor awaits all of them with a 5-second per-handler timeout. A handler that times out is treated as non-cancel and a warning is logged.

ctx.bus.on("save.before", async (e) => {
const ok = await checkSomething(e.mapId);
if (!ok) return { cancel: true, reason: "validation failed" };
});

Inter-mod events

Mods can emit and listen to the standard events freely. There is no sandboxing in v1 — all mods share the same bus. Use the commands API (via ctx.commands.register/execute) for direct request/response patterns where the bus would be awkward.

Custom event commands (not bus events)

Distinct from the editor event bus above, a mod can also register a custom RMXP event command that map makers insert into event pages — see events.registerCommand. These are not emitted on the bus; they are stored on the map and run in-game. Each registered command appears on its own named tab in the command picker; set page to title that tab (commands sharing a page collect under it) and pageDescription for its one-line strip.

Each mod command is saved as a standard RMXP Script command (code 355) whose parameters[0] is the literal Ruby that the command’s script(params, ctx) returns (e.g. pbCameraScrollTo(0, -4)). This keeps the map’s .rxdata round-tripping unchanged, passes validateEvent (355 is a known code), and runs in-game like any other event script — there is no runtime dispatcher or handler to register. The ctx argument says which event, page and list position the command is being written at, so the generated Ruby can depend on where the map maker dropped it.

Editing an event’s commands (not bus events)

Also distinct from the bus: a mod can read and rewrite the command list of an existing event page. events.getFull() returns every page with its list of commands; assign a new list and call events.update() to write it back (one undoable change):

const ev = ctx.events.getFull(mapId, eventId);
ev.pages[0].list = [ctx.events.createCommand(101, ["Hello"])];
ctx.events.update(mapId, ev);

update() appends the RMXP code-0 page terminator if your list lacks one, and leaves a page’s commands untouched if you omit its list. Full rules — appending to an existing list, validateEvent, and command indent — are in api-reference.md (events).