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.
| Event | Payload | Cancellable |
|---|---|---|
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 queryctx.map.info(...).map.unloaded— after a map tab is closed and removed from the editor.ctx.editor.activeMapId()returnsnullwhen the last map is closed.map.tile.changed— fires once for each single-tile edit. For brush strokes that write many tiles,map.batch.changedfires 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.rxdatafile. 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.changeis 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/yarenullwhen the cursor leaves the map canvas.clipboard.changed— fires after any copy, cut, or clipboard clear.tilesis 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.labelis the undo entry description.save.failed— fires when a save operation throws an error.erroris 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.tileCountis the number of tiles in the clipboard.stats.changed— fires periodically (~60s) with updated editor statistics snapshots.globalcontains lifetime stats,projectcontains current project stats (ornullif no project open).keybind.changed— fires when a keybind is changed via the settings dialog or thectx.keybindsAPI.actionIdis the affected action,oldKeyandnewKeyare 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).localeis 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 theapp.runGameshortcut), 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-inactionIdand 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 callse.stopImmediatePropagation()right after resolving, and it’s mounted onwindowin the capture phase before any mod loads — so a mod’s ownkeydownlistener, on whatever target or phase, never sees the event for a shortcut that actually fired. Does not cover mod-registered shortcuts (ctx.menu.registerMenuItem’sshortcut,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).