Drawdy › Docs › Build a plugin
Build a plugin
Add a tool to the Drawdy toolbar, read what's selected on the board, and create elements — in about fifteen minutes.
The plugin API isn't shipped yet. Everything below is the shape we're proposing, published early so you can argue with it before it hardens. Names and signatures will change.
Tell us what you'd build →What you'll learn
By the end of this guide you'll have a working plugin that adds a Tidy up tool to the toolbar. Select a few nodes, click it, and they snap into an even row. Along the way you'll touch the four things every plugin uses: the manifest, the plugin lifecycle, board events, and the node API.
Prerequisites
- Node.js 20 or later.
- A working install of Drawdy — the desktop app or a local dev build.
- Comfort with JavaScript. TypeScript is supported but not required.
Drawdy loads plugins from your vault folder at
<workspace>/.drawdy/plugins/<plugin-id>/. Each plugin is a folder with
a manifest and a bundled entry file — no install step, no registry required for local development.
Clone the sample plugin
Start from the sample rather than an empty folder — it ships with the build config and type definitions already wired.
Terminal# from your workspace folder
cd .drawdy/plugins
git clone https://github.com/drawdy/sample-plugin tidy-up
cd tidy-up && npm install && npm run dev
npm run dev watches your source and rebuilds main.js on every save.
Drawdy picks up the change the next time you reload the workspace.
Press ⌘R (Ctrl+R on Windows) inside Drawdy to reload plugins
without closing the app.
The manifest
Every plugin needs a drawdy.plugin.json beside its entry file. This is what the
app reads before it runs a single line of your code.
{
"id": "tidy-up",
"name": "Tidy up",
"version": "0.1.0",
"minAppVersion": "0.5.0",
"description": "Align the selected nodes into an even row.",
"author": "you",
"main": "main.js"
}
The id must match the folder name and be unique across the marketplace. Treat it
as permanent — changing it later looks like a different plugin to anyone who installed yours.
Your first tool
A plugin is a class with two lifecycle hooks. onload runs when the workspace
opens; onunload must undo anything you registered.
import { Plugin } from "@drawdy/sdk";
export default class TidyUp extends Plugin {
async onload() {
this.addTool({
id: "tidy-up",
name: "Tidy up",
icon: "align-horizontal",
onRun: () => this.tidy()
});
}
onunload() {
// tools added with addTool are removed automatically
}
}
Save, reload, and a new button appears at the end of the toolbar. It does nothing yet.
Anything you attach by hand — timers, DOM listeners, network polling — must be torn down in
onunload. A plugin that leaks listeners will keep firing after the user disables it.
React to selection
Most plugins act on whatever the user has selected. board.selection gives you the
current set; board.on("selection", …) tells you when it changes.
async onload() {
this.addTool({ id: "tidy-up", name: "Tidy up", onRun: () => this.tidy() });
// disable the tool unless two or more nodes are selected
this.register(
this.board.on("selection", (nodes) => {
this.setToolEnabled("tidy-up", nodes.length >= 2);
})
);
}
Wrapping the subscription in this.register() hands the teardown to Drawdy — the
listener is removed for you when the plugin unloads.
Draw on the board
Now the actual work. Read the selection, sort it left to right, and write new positions back inside a single transaction so the whole change is one undo step.
src/main.tstidy() {
const nodes = this.board.selection;
if (nodes.length < 2) return;
const sorted = [...nodes].sort((a, b) => a.x - b.x);
const gap = 32;
let x = sorted[0].x;
this.board.transact("Tidy up", () => {
for (const node of sorted) {
node.moveTo({ x, y: sorted[0].y });
x += node.width + gap;
}
});
}
Every mutation inside board.transact() collapses into a single entry in version
history. Without it, moving eight nodes costs your user eight presses of ⌘Z.
Ship it
Build once, then publish. A release needs three files at the root of your repo:
manifest, main.js, and a README.
npm run build
git tag 0.1.0 && git push --tags
Tag the release and open a pull request against the community plugin list. Once it's merged, your plugin shows up in the in-app marketplace and users install it with one click.
The API surface, in one screen
The whole proposed surface, so you can judge it quickly:
@drawdy/sdkclass Plugin {
onload(): void | Promise<void>
onunload(): void
addTool(opts): void // id, name, icon, onRun
setToolEnabled(id, on): void
register(teardown): void // auto-cleanup on unload
get board: Board
}
interface Board {
selection: Node[]
nodes: Node[]
on(event, handler): Teardown // "selection" | "change" | "pointer"
create(spec): Node // { type, x, y, width, height, text }
transact(label, fn): void // one undo step
}
interface Node {
id: string; type: string
x: number; y: number; width: number; height: number
moveTo(pos): void
update(props): void
remove(): void
}
What's next
That's the whole model: a class, a manifest, a board, and nodes. If something you need doesn't fit in it, that's the useful thing to tell us — the API is still soft enough to change shape around real plugins.
Open an issue with the plugin you'd build and what's missing. github.com/Porches2/Drawdy