# Phaser Editor v5 — Plugin Template
A minimal, self-contained template for building **third-party plugins** for
[Phaser Editor v5](https://phasereditor2d.com).
This template ships one example plugin (`myplugin.example`) that contributes a
custom **command** to the editor. Use it as the starting point for your own
plugin: clone it, rename it, and grow it.
---
## Developing with AI assistance
This template is set up so an AI coding assistant can help you build plugin
features effectively:
- **[`AGENTS.md`](AGENTS.md)** — an entry point that orients any AI agent
(Cursor, Claude Code, etc.) on the architecture, conventions, and build.
- **[`docs/`](docs/)** — task-focused recipes for humans and AI alike: an
[extension-points catalog](docs/extension-points.md) plus how-tos for
[commands](docs/recipes/add-a-command.md),
[custom editors](docs/recipes/add-a-custom-editor.md),
[content types & the New File wizard](docs/recipes/add-a-content-type-and-new-file.md),
[Inspector properties](docs/recipes/add-inspector-properties.md), and
[theme-correct styles](docs/recipes/add-styles.md).
- **[`.cursor/skills/phaser-editor-plugin`](.cursor/skills/phaser-editor-plugin/SKILL.md)**
— a Cursor skill that auto-applies when you ask the agent to add plugin
functionality and points it at the right doc and example.
The bundled `types/*.d.ts` give the AI the editor's real API surface, and
`myplugin.example` is a complete worked example to copy from. Just ask your
assistant something like *"add a command that …"* or *"add an editor for `.foo`
files"* and it will follow these docs.
---
## How a plugin works
A Phaser Editor plugin is just a **folder** containing:
- a `plugin.json` manifest, and
- one or more JavaScript files (and optional CSS) listed in that manifest.
Loading happens in two steps:
1. **Discovery (server side).** When the editor starts, the server scans its
plugin directories. For every folder that has a `plugin.json`, it injects the
listed `scripts` and `styles` into the editor page.
2. **Registration (client side).** When your script runs in the page, it calls
`colibri.Platform.addPlugin(...)` to register your plugin instance. During
startup the editor then calls your plugin's `registerExtensions(reg)` method,
where you contribute commands, menus, editors, views, and so on.
There is no webpack or runtime npm dependency. The build is plain TypeScript:
each plugin compiles to a single concatenated file (`_out/<id>.js`) via `tsc`
with `outFile`.
This template compiles against a **bundled snapshot** of the editor's API types
(`types/colibri.d.ts`), so you do **not** need a checkout of the editor source
to build your plugin.
---
## Project structure
```text
phaser-editor-5-plugin-template/
├── README.md # this file
├── AGENTS.md # AI-agent entry point (architecture + conventions)
├── docs/ # developer recipes (commands, editors, styles, ...)
├── .cursor/skills/ # Cursor skill for plugin development
├── package.json # build scripts + the typescript devDependency
├── update-types.mjs # copies all editor plugin .d.ts into types/
├── tsconfig.json # solution file: references the example plugin's src
├── tsconfig-base.json # shared compiler options
├── types/ # bundled API snapshots, one .d.ts per editor plugin
│ ├── colibri.d.ts # core editor API
│ ├── phasereditor2d.files.d.ts # the New File wizard (Files plugin)
│ ├── phasereditor2d.phaser.d.ts # Phaser + scene types
│ └── ... # every other editor plugin
└── myplugin.example/ # the example plugin (folder name == plugin id)
├── plugin.json # manifest: id + scripts + styles
├── styles/
│ └── TodoEditor.css # theme-scoped styles for the Todo editor
├── _out/ # build output (generated; git-ignored)
│ └── myplugin.example.js
└── src/
├── tsconfig.json # outFile -> ../_out/myplugin.example.js
├── ExamplePlugin.ts # plugin class: registers the command, content type, editor
└── editors/ # the custom "Todo" editor example
├── TodoModel.ts # data model + ".todo" content type id
├── TodoEditor.ts # the editor, viewer, commands, toolbar
├── TodoEditorOutlineProvider.ts # feeds the Outline view
├── TodoEditorProperties.ts # Inspector sections (name/description/state)
└── NewTodoFileExtension.ts # "New Todo File" wizard entry
```
---
## Prerequisites
- [Node.js](https://nodejs.org) (any recent LTS) and npm.
- An installed copy of **Phaser Editor v5** (desktop or the CLI launcher).
---
## Build
```bash
npm install
npm run build
```
This produces `myplugin.example/_out/myplugin.example.js`.
Other scripts:
- `npm run watch` — rebuild on every change (great while developing).
- `npm run clean` — remove the build output.
---
## Install / test the plugin
After building, make the editor aware of the plugin in any **one** of these
ways:
### Option A — User plugins folder
Copy the plugin folder into your user plugins directory:
```bash
cp -r myplugin.example ~/.phasereditor2d/plugins/
```
Restart the editor. The plugin loads automatically.
### Option B — `-plugins` launcher flag (best for development)
Point the launcher at this template's root directory. The editor scans each
subfolder for a `plugin.json`; `types/` is ignored because it has none:
```bash
PhaserEditor -plugins /path/to/phaser-editor-5-plugin-template
```
Combine with `npm run watch` to iterate quickly — rebuild, then reload the
editor page.
### Option C — Per-project config
In a game project, add the plugin's path to `phasereditor2d.config.json`:
```json
{
"plugins": ["/path/to/phaser-editor-5-plugin-template/myplugin.example"]
}
```
### Running the command
Once loaded, run the example command in either way:
- Open the **command palette** with `Ctrl+K`, search for **"Hello From Plugin"**.
- Or press the shortcut **`Ctrl+Alt+H`**.
You should see a "Hello from myplugin.example!" message.
---
## Renaming / cloning the plugin
To turn this into your own plugin, change the id **everywhere** so the manifest,
the compiled file, the namespace, and the build reference all stay in sync:
1. Rename the folder `myplugin.example/` to your plugin id, e.g. `com.acme.tools`.
2. In `com.acme.tools/plugin.json`: set `"id"` and the script path in `"scripts"`.
3. In `com.acme.tools/src/tsconfig.json`: update `outFile` to
`../_out/com.acme.tools.js`.
4. In `com.acme.tools/src/ExamplePlugin.ts`: rename the `namespace`, the
`super("...")` id, and the command/category ids.
5. In the root `tsconfig.json`: update the `references` path.
Use a unique, namespaced id (reverse-domain style is a good convention) to avoid
clashing with other plugins. Command ids follow the convention
`<pluginId>.<ActionName>`.
---
## Adding more commands
Add more `manager.add({ ... })` calls inside the `CommandExtension` in
[`ExamplePlugin.ts`](myplugin.example/src/ExamplePlugin.ts). Each command takes:
- `command`: metadata (`id`, `name`, `tooltip`, `category`).
- `handler`: behavior — `executeFunc(args)` runs the command, and an optional
`testFunc(args)` controls when it is enabled (e.g. only in a specific editor).
- `keys`: an optional keyboard shortcut (`control` / `shift` / `alt` / `meta`
flags plus a `key`).
The `args` passed to the handlers give you the active part, editor, selection,
and window — see `colibri.ui.ide.commands` in `types/colibri.d.ts`.
### Other extension points
Commands are just one extension point. Browse `types/colibri.d.ts` for more,
including:
- `colibri.ui.controls.menus` (`MenuExtension`) — contribute menu entries that
reference your command ids.
- `colibri.ui.ide.EditorExtension` / `WindowExtension` — custom editors and
views.
- `colibri.core.ContentTypeExtension` / `colibri.core.io.FileStorageExtension` —
content types and file storage.
All of them are contributed the same way: `reg.addExtension(new ...Extension(...))`
inside `registerExtensions`.
---
## Custom editor example: the Todo editor
The template also ships a small but complete **custom editor** that opens
`.todo` files. It is a **rich HTML-based editor** (it builds its own DOM instead
of using a tree viewer), and it demonstrates the most common building blocks of
a Phaser Editor editor: a content type, the Outline view, the Inspector
(properties) view, and theme-correct CSS, with selection kept in sync between
all three.
### Try it
1. Build the plugin and make the editor aware of it (see the sections above).
2. Create a new todo file with the **New File** wizard, in any of these ways:
- Right-click a folder in the **Files** view -> **New...** -> **Todo File**, or
- press **Ctrl+Alt+N** and pick **Todo File**, or
- use the **New File** button in the main toolbar.
A `tasks.todo` file is created (pre-filled with one example task) and opens
automatically in the **Todo Editor**, which pretty-prints each task (name +
colored state).
3. Use the editor toolbar (or right-click for the context menu) to **Add Task**,
**Delete**, **Move Up**, and **Move Down**. Pressing `A` adds a task; `Delete`
removes the selected ones.
4. Open the **Outline** view to see every task listed by name.
5. Open the **Inspector** view and select a task (in the editor or the outline)
to edit its **name**, **state**, and **description**. Changes are reflected
immediately and mark the editor dirty (save with `Ctrl+S`).
### How it is wired
```mermaid
flowchart LR
file[".todo file"] --> ct["Content type<br/>ContentTypeResolverByExtension"]
ct --> factory["ContentTypeEditorFactory"]
factory --> editor["TodoEditor<br/>(FileEditor + HTML task cards)"]
editor -->|"getEditorViewerProvider('Outline')"| outline["TodoEditorOutlineProvider<br/>-> Outline view"]
editor -->|"getPropertyProvider()"| props["TodoEditorPropertySectionProvider<br/>-> Inspector view"]
editor -->|selection| outline
outline -->|onViewerSelectionChanged| editor
editor -->|selection| props
```
- **Content type** ([TodoModel.ts](myplugin.example/src/editors/TodoModel.ts)):
`colibri.core.ContentTypeResolverByExtension` maps the `todo` extension to the
content type id `CONTENT_TYPE_TODO`. The data model (`TodoModel` / `TodoTask`)
serializes to/from JSON.
- **Editor** ([TodoEditor.ts](myplugin.example/src/editors/TodoEditor.ts)):
extends `colibri.ui.ide.FileEditor` and builds its **own HTML** in
`createPart` - a scrollable list of task "cards" (name + colored state badge +
description). It tracks its own selection (`_selectedTasks`), highlights the
selected cards, supports Ctrl/Cmd-click for multi-select, loads the file in
`createPart`, saves JSON in `doSave`, and exposes `Add / Delete / Move Up /
Move Down` commands via a toolbar and context menu.
- **Styles** ([TodoEditor.css](myplugin.example/styles/TodoEditor.css)):
theme-correct CSS registered in `plugin.json`. Layout is theme-agnostic;
colors are scoped per theme class, grouped for the dark-like themes (`dark`,
`phaserstudio`) and the light-like themes (`light`, `lightBlue`, `lightGray`).
- **Outline**
([TodoEditorOutlineProvider.ts](myplugin.example/src/editors/TodoEditorOutlineProvider.ts)):
an `EditorViewerProvider` returned for the `"Outline"` key. It lists tasks by
name and pushes outline selection back into the editor.
- **Inspector**
([TodoEditorProperties.ts](myplugin.example/src/editors/TodoEditorProperties.ts)):
a `PropertySectionProvider` with a single `PropertySection` that renders the
name (text), state (menu button), and description (text area). It is returned
by both the editor (`getPropertyProvider`) and the outline provider
(`getPropertySectionProvider`), so it works whichever view has focus.
- **New File wizard**
([NewTodoFileExtension.ts](myplugin.example/src/editors/NewTodoFileExtension.ts)):
extends `phasereditor2d.files.ui.dialogs.NewFileContentExtension` so a
**Todo File** entry appears in the New File wizard (Ctrl+Alt+N), the Files
view **New...** menu, and the New File toolbar button. It generates the
initial JSON content and the editor opens the file automatically. This is the
same mechanism used by the built-in **Scene File** and **Asset Pack File**
wizards.
Selection routing: when you click a card, the editor calls `setSelection(tasks)`
on its `Part`, which the Inspector view listens to, and it mirrors the selection
to the outline provider. When you click in the Outline, the provider's
`onViewerSelectionChanged` pushes the selection back into the editor (which then
updates the inspector). A `syncOutline` flag prevents feedback loops.
> Note on bundled types: the editor compiles against `colibri.d.ts`, and it
> returns the outline provider by comparing the key against the literal
> `"Outline"` (the value of `OutlineView.EDITOR_VIEWER_PROVIDER_KEY`) so it
> needs no outline/inspector types. The only feature that needs a second
> declaration file is the New File wizard, because its base class lives in the
> Files plugin — hence the bundled `phasereditor2d.files.d.ts`.
---
## Updating the types
The files in `types/` are **snapshots** of the editor API (one `.d.ts` per
editor plugin), so this template compiles against the **full editor API**
without an editor checkout. They are taken from a specific editor version.
To refresh them from your editor source, set `PHASEREDITOR5_HOME` to the folder
that contains `phaser-editor-v5` and run the bundled script:
```bash
export PHASEREDITOR5_HOME=/path/to/parent # so the source is at
# $PHASEREDITOR5_HOME/phaser-editor-v5/workbench
npm run update-types
```
The script copies every `<plugin>/_out/<plugin>.d.ts` from
`$PHASEREDITOR5_HOME/phaser-editor-v5/workbench/source/editor/plugins` into
`types/`. The editor must have been built (its `_out` folders must exist).
Because `types/*.d.ts` are all included by
[`myplugin.example/src/tsconfig.json`](myplugin.example/src/tsconfig.json), every
plugin's API is available to your code immediately after running the script.