API Changelog
The mod API follows semver:
- Major — breaking changes. Existing mods targeting the previous major version are routed through a compatibility shim. If no shim exists, the mod is refused with a clear error in the Mod Manager.
- Minor — a large additive batch (a whole new
ctxsurface). Old mods keep working without changes. - Patch — anything else additive (a new method, event, or field) plus behaviour fixes mods can observe.
Every API change gets a new version — including patch. Set manifest.apiVersion to the version
that introduced the newest thing your mod uses, and an editor older than that refuses the mod instead
of running it into a crash on a method it doesn’t have:
- Installing / updating from the Marketplace is blocked, with the required version named on the card.
- An already-installed mod loads as
errorin the Mod Manager (apiVersion X not supported — this editor provides Mod API Y) and never activates.
So keep apiVersion as low as your mod actually needs — it is the minimum editor requirement, not a
“latest” badge. Targeting an older version is always safe; newer editors keep loading it.
When a major bump happens, this file gets a section with the new shape and a link to a migration guide.
1.0.1
Additive only — mods targeting 1.0.0 keep working untouched. Declare "apiVersion": "1.0.1" only
if you use something from this section.
Added
-
ctx.ui.decorate(selector, apply): take over any element of built-in editor UI that matches a CSS selector — the ones on screen now and every one mounted later, so a dialog opened later is decorated the same as one already open. The callback receives a realHTMLElement(append to it, restyle it,replaceWith()it) and may return a cleanup, run when the element leaves the DOM or the mod unloads. Each element is decorated once per decorator, so re-renders never stack duplicates. OneMutationObserverbacks every decorator and stays disconnected while none are registered.[data-ms-part](dialog,menubar,toolbar,statusbar,panel-header,canvas) is the stable selector contract, shared with the theme system; component class names work but are internal. See api-reference.md (ui→ Extending built-in editor UI). -
ctx.ui.registerSlot(slot, render, opts?): the editor’s named extension points, for the cases where the DOM alone isn’t enough — the payload carries ids and setters.fog.config,tileset.editor.tile,event.command.form,event.command.form.<code>(one per RMXP command code) andproperties.panel.slot.data()is a getter (the host element is reused across re-renders) andslot.onUpdate(fn)fires when it changes;{ replace: true }hides the slot’s built-in content,{ order: n }sorts multiple registrations. -
New
ctx.simulatorsub-context:registerScriptHandler(match, handler)claims Script bodies the Game Simulator cannot run — the Script command (355), a move-route Script (move code 45) and a Script conditional branch (kind 12, where the handler’s boolean is the branch’s answer).registerCommandHandler(code, handler)implements or overrides an event command code; mod handlers run before the built-in implementation, and returningfalsedeclines. Handlers receive a narrowSimApi(switches, variables, self switches,character()/characters(),wait,showText,log) rather than the internal runtime. A throwing handler is caught, logged to the simulator panel and counted as handled. See api-reference.md (simulator). -
PanelDef.defaultSize:{ width, height }(px), sets the floating window’s size the very first time a panel opens — before it has a dock position or the user has resized it. Default unchanged ({ width: 480, height: 360 }). Additive: mods that don’t set it see no change. See api-reference.md (ui.registerPanel). -
New bus event
keybind.triggered:{ actionId: string }, fires when the global shortcut dispatcher resolves a keydown to a built-in action, right before it callse.stopImmediatePropagation(). That call is why a mod’s ownkeydownlistener — onwindowordocument, any phase — never sees the event for a shortcut that actually fired: the dispatcher is mounted onwindowin the capture phase before any mod loads, so it always runs first. This event is the only reliable way to know “the keyboard was just used for X”; don’t try to reconstruct it by listening for rawkeydownyourself. Doesn’t cover mod-registered shortcuts (registerMenuItem’sshortcut,ui.registerShortcut) — those resolve on a separate path. See events-reference.md. -
ctx.editor.viewOptions()/setViewOptions()gainshowEventCells, matching the editor’s “Toggle Event Cells” view option (view.toggleEventCellskeybind) — previously unreadable and unwritable by mods. Additive: existing destructuring of the returned object keeps working. See api-reference.md (editor). -
New bus event
game.launch:{ gameRoot: string }, fires once per Run Game invocation (toolbar button, menu item, or theapp.runGameshortcut) regardless of outcome — a reliable “the user asked to run the game” signal, since there’s no other way to observe that action (it’s momentary, not a toggleable state). See events-reference.md. -
Toast action buttons (
ctx.ui.showToast).ToastOptionsgains optionalactionandsecondaryAction, each{ label, onClick }— up to two clickable buttons on a toast, secondary rendered left of primary. Clicking either dismisses the toast, then runsonClick. Also new:ctx.ui.openKeyboardShortcuts(actionId?)opens the editor’s native Keyboard Shortcuts dialog (same as Help → Keyboard Shortcuts…); pass a built-inactionIdand it opens pre-scrolled to that row, already listening for a keypress — pair it with a toast button to send the user straight into rebinding a specific action. Additive: mods that never setaction/secondaryActionsee no change. See api-reference.md (ui). -
ctx.selectors.pickGraphic(..., { allowTileSelect: true }). Offers tile picking, but only once the chosen graphic is a tileset (one living inGraphics/Tilesets): the preview then gains a tile grid, click picks one tile and drag picks a block. Any other graphic is unaffected, so the option is safe to leave on. The selection comes back onGraphicPickResult.srcRectas{ x, y, w, h }in source-image pixels — always a whole number of 32px tiles — withw/hof 0 meaning the whole image (which is also what you get without the option), so existing code reads unchanged. The editor treats the selection as if it were the whole image: the sheet grid divides it anddirection/patternindex inside it, and picking tiles setssheetCols/sheetRowsto match. In-game it needs the MakerStudio plugin — the editor’s own event/picture graphics carry the same field, so a project that has the plugin honours what you picked. -
ctx.theme(ThemeCtx). Register editor themes:register({ id, name, base, vars?, css?, canvas? }),apply(id | null),current(),list(), andassetUrl(relPath)for turning a file in your mod folder into adata:URI. One theme is active at a time, chosen in View → Theme and remembered between sessions; a theme’s rules are scoped to it, so registering one changes nothing until it is applied, and unloading your mod removes it.canvas.imageis painted by the map renderer itself, under the map — no transparent--canvas-bgand no z-ordered overlay. Stable hooks for the CSS:data-ms-part="menubar|toolbar|statusbar|panel-header|dialog|canvas". A theme declaresdark/lightvariants to follow the editor’s Dark Mode toggle (one entry in View → Theme, two looks); declaring only one of them — or neither — forces that scheme and locks the toggle while the theme is active. -
ctx.fs.readModFileBytes(relPath). Raw bytes from your own mod folder, for images and fonts. -
manifest.tags(ModManifest). Optionalstring[]of Marketplace search/filter tags, e.g.["tilesets", "ui"]. The editor records them; the publishing workflow copies them into yourindex.jsonentry, slugging each one to the registry’s^[a-z0-9-]+$(so"Terrain Tags"becomesterrain-tags) and keeping the first 8. Additive: omit it and nothing changes — the registry maintainer picks your tags on review, as before. -
Mod command placement context (
ModCommandContext,ctx.events.registerCommand).script,summaryand a field’sdisabled/hiddenpredicates now receive a second argument saying where the command sits:{ mapId, eventId, pageIndex, index, count, indent }(eventId/pageIndexarenullin Database → Common Events).index === 0is the first command,index === count - 1the last, andindent > 0means it is nested inside a conditional branch or loop; while the command is being inserted the context describes the spot it is about to land on. Additive — callbacks that ignore the second argument are unaffected. See api-reference.md (events.registerCommand). -
Mod command tabs (
ModCommandDef,ctx.events.registerCommand).pageis now functional: it titles the command’s own tab in the event-command picker, and commands sharing the samepagestring collect under one named tab (omit it and they group under the mod id). New optionalpageDescriptionfills the one-line description strip shown beneath that tab while it is active — among commands sharing a page, the first one that sets it wins. Additive: existing mods keep working; a command with nopagesimply gets a tab named after its mod. See api-reference.md (events.registerCommand). -
Tileset Editor glyph styling (
ctx.tileset).registerPrioritytakes an optionalcolor(any CSS colour) that paints that level’s marker on the tile and its chip in the Priority dropdown; without one the built-in five-colour cycle continues, so id 6 still reuses id 1’s colour. NewsetGlyphStyle(style)restyles every marker the Tileset Editor draws —passageOpen/passageBlocked/passagePartial,bush,counter,terrain, the cyclingprioritylist,neutral(priority 0 and flags-off), plusshadowColor,shadowBlurandstrokeWidth(the last two as fractions of the tile cell size;shadowBlur: 0turns the shadow off). Every field is optional and merged over the defaults, later registrations win per field, and the returnedDisposablerestores the defaults on unload. Additive: mods that never call it see no change. See api-reference.md (tileset). -
docs/app-strings.json— the editor’s full list of translatable source strings, exported empty-valued and sorted, as the starting template for a translation mod: fill in the values and pass it as thedictofctx.i18n.registerLocale(). Regenerated each editor release, so diffing it against your dict surfaces the new strings. Docs only, no API change. See api-reference.md (i18n→ Translating the whole editor).
Fixed
-
Empty dictionary values now fall back to English (
ctx.i18n).t()treated""as a real translation and rendered blank text; it now falls through to the app dictionary and then the source string. This is what makes a partially filledapp-strings.jsonshippable. -
Event command lists are now readable and writable (
ctx.events).PublicEventPagecarrieslist?: PublicEventCommand[].events.getFull()returns each page’s commands (it used to drop them, so mods could never read what an event does), andevents.update()writes back thelistyou set on a page (it used to ignore it, so commands could not be written at all). Omit a page’slistto leave its existing commands untouched;update()appends the RMXP code-0 page terminator when your list lacks one, so mod-built lists don’t need it. This makesevents.createCommand()usable — it previously produced command structs with nowhere to put them — and fixesevents.validateEvent(), which never saw a command list and so reported{ valid: true, errors: [] }for every event, including ones with unknown command codes. Additive: mods written against 1.0.0 keep working unchanged. See api-reference.md (events). -
PublicEventPage.always_on_bottom(ctx.events). Maker Studio’s mirror of RPG Maker XP’salways_on_top: the event draws below every character.getFull()reports it andupdate()writes it, like every other page field.always_on_topwins when both are set, and the flag only takes effect in-game with the MakerStudio plugin installed. Additive: mods that never touch the field keep working unchanged. See api-reference.md (events). -
apiVersionis now enforced down to the patch number. The editor used to compare only major and minor, so a mod targeting a version its editor didn’t have loaded anyway and failed later — at the first call to a method that wasn’t there. It is now refused up front (blocked in the Marketplace,errorin the Mod Manager), which is what makes patch-level API versions a real guarantee.
v1.0.0 — Initial Release
First public mod API, shipping with Maker Studio 1.0. A stable ctx surface lets
mods extend the editor end-to-end: editing maps, adding tools and UI, hooking the
event bus, and shipping custom content through to the game.
The full method/type reference lives in api-reference.md and mod-api.d.ts; every editor event is documented in events-reference.md. This entry lists the essential capabilities, not the exhaustive surface.
Essential features
- Mod lifecycle & manifest — each mod ships a
ModManifest(id,version,apiVersion,mainentry) withactivate(ctx)/deactivate()hooks. Optional multi-authorauthorsand a unifiedrequiresarray (other mods and/or Essentials plugins, topo-sorted on load). Single-file, CommonJS, and multi-file ESM mods are all supported. - Map editing — read/write tiles and per-tile data, query and manage layers (native, extended, shadow), selections with transforms, undo grouping and scopes, a tile clipboard, and full map CRUD (create, delete, resize, rename, reparent).
- Tilesets — tileset images, tile properties (passage / priority / terrain tag), tileset CRUD, and mod-registered custom terrain tags & priorities that appear named in the Tileset Editor and are written verbatim to the game data.
- Graphic layer groups —
ctx.fog,ctx.panorama, and mod-registered custom layer groups (ctx.layerGroups) with arbitrary in-game priorities. All support aparallaxcamera-follow factor, persist per map inside@extended_layers, and render in-game via the bundled plugin — even without the mod installed. - Events — list / create / move / update RMXP-style events, plus
ctx.events.registerCommandto add custom event commands with declarative forms (number, text, select, coordinate, graphic, audio, …) that compile to runnable in-game Script commands and stay re-editable. - Custom UI — register editing tools, menu items (with icons & shortcuts), dockable panels, dialogs (confirm / input / custom), toasts, Canvas2D overlays, context-menu items, toolbar / status-bar items, and global shortcuts. Panel and dialog UI inherits the editor’s theme CSS variables.
- Selectors — promise-based modal pickers for every RPG record (actor, class, skill, item, weapon, armor, enemy, troop, state, animation, common event, switch, variable, map, event, tileset, audio, graphic, keyboard button, coordinate).
- Project data — read-only access to project record lists (actors, classes, skills, items, weapons, armors, enemies, troops, states, animations, common events), switch / variable name arrays, and the map-info list.
- Event bus — 25 stable editor events;
save.beforeandpaste.beforeare cancellable. - Lifecycle hooks —
onMapLoad,onSave,onActivate,onDeactivate,onToolChange,onLayerChange,onUndo/onRedo,onBrushChange,onTilesetChange. - Filesystem & persistence — path-scoped filesystem (mod folder + project
folder), per-mod K/V
storage, OS textclipboard(system-wide), and a namespacedlog. - Runtime queries —
ctx.mods/ctx.pluginsfor feature detection and soft dependencies,ctx.keybindsto read and modify keyboard shortcuts, andctx.statsfor editor usage statistics plus custom mod statistics. - Direct Tauri access — mods can invoke registered backend commands via
window.__TAURI__.core.invoke(...)for file I/O, image / tileset work, and native dialogs.
Stability
CI runs the bundled example mods as smoke tests and asserts their ModContext
shape snapshot on every PR — accidental changes to the contract surface fail the
build.