Skip to main content

Writing a Plugin

This walkthrough builds a small but complete plugin — a "sticky-note" object type with property fields, a keyboard shortcut, and event logging — using nothing but the public registry API every shipped plugin (including @rifrocket/fdt-plugin-qrcode, whose real source is a great companion reference) is built on.

1. Start from the shape every plugin has

import type { EditorPlugin } from "@rifrocket/fabricjs-design-tool";

export const stickyNotePlugin: EditorPlugin = {
name: "sticky-note",
install(engine) {
// registrations go here
},
};

name must be unique across everything installed on a given engine — engine.use() throws if you reuse one. install(engine) runs synchronously and receives the live CanvasEngine.

2. Register the object type

import { Rect } from "fabric";
import { ColorField, TextField } from "@rifrocket/fdt-properties";

interface StickyNoteConfig {
left?: number;
top?: number;
text?: string;
fill?: string;
}

export const stickyNotePlugin: EditorPlugin = {
name: "sticky-note",
install(engine) {
engine.registry.registerObjectType<StickyNoteConfig>("sticky-note", {
create: (config) => {
const note = new Rect({
left: config.left ?? 50,
top: config.top ?? 50,
width: 160,
height: 160,
fill: config.fill ?? "#fef08a",
});
note.set("shapeKind", "sticky-note");
return note;
},
// Registered atomically with the type — see extension-points/custom-property-fields.
propertyFields: [
{ key: "fill", label: "Color", component: ColorField },
],
});
},
};

Registering propertyFields in the same call that defines the type (rather than in a separate follow-up plugin) is the pattern every shipped object-type plugin follows — it keeps a type and its editable fields atomic, with no installation-order dependency to get right.

3. Add a keyboard shortcut

install(engine) {
engine.registry.registerObjectType(/* ... as above ... */);

engine.shortcuts.register(
"ctrl+shift+n",
() => { void engine.addObjectOfType("sticky-note", {}); },
"Add sticky note",
);
},

4. Log when one is added (event middleware)

install(engine) {
// ...

engine.events.on("objects:changed", (objectIds) => {
// real code would diff against the previous id list; this is illustrative
console.log("objects changed, now:", objectIds.length);
});
},

5. Install it

import { stickyNotePlugin } from "./stickyNotePlugin";

engine.use(stickyNotePlugin);
// or, alongside other plugins with dependency ordering:
engine.useAll([shapesBasicPlugin, stickyNotePlugin]);
<Editor plugins={[shapesBasicPlugin, stickyNotePlugin]} />

6. (Optional) Typed object-type ids

declare module "@rifrocket/fabricjs-design-tool" {
interface ObjectTypeMap {
"sticky-note": StickyNoteConfig;
}
}

This gets engine.addObjectOfType("sticky-note", ...) autocomplete/typo-checking on the type id anywhere it's used, without core (or any other plugin) needing to know about sticky-note ahead of time — the same module-augmentation pattern plugin-shapes-basic, plugin-image, and plugin-qrcode each use in their own objectTypeMap.ts.

What you didn't need

No file inside packages/core or packages/react needed to change. Every extension point used above — registerObjectType, registerPropertyFields (implicitly, via the same call), engine.shortcuts.register, engine.events.on — is the exact public API a first-party plugin uses. See Extension Points for the full reference on each seam, and Plugins Overview for how the 17 official plugin packages are organized.