Building an extension
Panels, tools, fields and routes added to the editor.
This covers a panel, a tool, a command, an inspector section, a field type or a route added to the editor, yarn editor <game>. Nothing a game runs imports an extension, so the game runs the same with the editor and without it.
Where it goes
| It is | Home |
|---|---|
| How the editor runs: shell, files, undo, design system | editor/ |
| Time: stepping, going back, replay | The recorder |
| What a plugin or addon owns | plugins/<p>/<p>.extension.tsx or addons/<a>/<a>.extension.tsx |
| One game's kinds, level or states | games/<game>/<game>.extension.tsx, <kind>.extension.tsx, beside one of its plugins, or games/<game>/extensions/<name>/ |
| Rows, kinds, seats or files in general | extensions/<name>/ |
A plugin's extension is on while a scene of the open game names the plugin. A game's own is on while that game is open. The editor finds extensions by file name; there is nothing to register.
The files
<name>.extension.tsxexports a function named<name>, handed the editor. Its header lists what it adds and its commands.<name>.components.tsxholds its React components, built fromeditor/ui/.<name>.route.ts, only when it needs the disk or a process, adds routes served at/api/x/<name>/<route>.
The work, in order
- Find the home above.
- Write
<name>.extension.tsxwith its header. Write each action as one function taking the editor, the edits and world values. - Register the panel, tool and commands with
ed.panel,ed.toolanded.command, each calling that one function. Anything that outlives a call goes toed.onDrop. - Write every change through the edits: a running row with
editRow,spawnorend, a file witheditFileoreditFiles, one value at a time. A panel's action runs ined.together, so one action is one undo. - Register any field type it defines with
ed.field('<owner>.<type>', Component)and bind it withed.bindored.bindFile. - Write its test beside it and run
yarn vitest run <file>, thenyarn tscandyarn lint. - Open it in the editor and look at it in Chrome.
Commands
Every command is also a tool on the editor's MCP endpoint, /api/mcp, named by its id with each . as _. A command that takes arguments has a z.object as its input, with a description on it and on every field, and no .transform. It takes world points, never canvas pixels.
Panels
Build every panel from the design system in editor/ui/: PanelBody, Button, NumberField, PropertyGrid and the rest. An extension writes no CSS of its own; a colour drawn on a canvas is read from the theme. A component names every topic it reads in useEditor(topics). A viewer's preferences stay in the browser, never in a scene file.
Checking it
<name>.extension.test.tsxboots a scene it writes under the recorder, runs the command or drives the panel, and checks that only the one value changed in the file, that undo puts the file's bytes back exactly, one undo per action, and no error line ined.log.- A test that attaches the game view is
<name>.extension.view.test.tsx, run withyarn test:view <file>. - A route has its own
<name>.route.test.ts. - In
yarn editor <game>, edits to the extension reload the page; check the panel in Chrome, make a change, and undo it.