Custom Object Types
ObjectTypeRegistry is how every shape, image, and QR code in this framework is defined — including the built-in ones. There's no closed, editable-only-by-editing-the-library factory class; registering a new type is a plugin install() call away.
interface ObjectTypeDefinition<TConfig = unknown> {
create(config: TConfig): FabricObject | Promise<FabricObject>;
propertyFields?: PropertyFieldDefinition[];
}
Registering a type
import type { EditorPlugin } from "@rifrocket/fabricjs-design-tool";
import { Rect } from "fabric";
interface StickyNoteConfig {
left?: number;
top?: number;
text?: string;
}
const stickyNotePlugin: EditorPlugin = {
name: "sticky-note",
install(engine) {
engine.registry.registerObjectType<StickyNoteConfig>("sticky-note", {
create: (config) => {
const rect = new Rect({ left: config.left ?? 0, top: config.top ?? 0, width: 160, height: 160, fill: "#fef08a" });
return rect;
},
});
},
};
engine.use(stickyNotePlugin);
const note = await engine.addObjectOfType("sticky-note", { left: 50, top: 50 });
create() is always async-compatible — the registry always awaits it, whether or not your implementation actually needs to (real types often do: QR codes generate asynchronously, images decode a URL, SVG import parses a document).
Registering + adding, without history
// Adds through the history-tracked path (undoable):
await engine.addObjectOfType("sticky-note", { left: 50, top: 50 });
// Creates without adding to the canvas or history — useful if you need to inspect/modify
// the object before it becomes part of the document:
const note = await engine.createObject("sticky-note", { left: 50, top: 50 });
Duplicate registration and replacement
engine.registry.objectTypes.register("rect", { create: ... }); // throws: "rect" is already registered
engine.registry.objectTypes.replace("rect", { create: ... }); // atomic unregister+register, no throw
engine.registry.objectTypes.has("rect"); // boolean
engine.registry.objectTypes.list(); // string[] of every registered type id
engine.registry.objectTypes.unregister("rect");
Adding property fields to a type you don't own
If you're building a plugin that adds fields to a type another plugin registers (rather than defining the type yourself), use registerPropertyFields and declare dependsOn so useAll() installs your plugin after the type exists:
const brandFieldsPlugin: EditorPlugin = {
name: "brand-fields",
dependsOn: ["shapes-basic"],
install(engine) {
engine.registry.registerPropertyFields("rect", [{ key: "brandVoice", label: "Tone", component: SelectField }]);
},
};
See Custom Property Fields for the field-definition shape in full, and Writing a Plugin for an end-to-end walkthrough that registers both a type and its fields together (the preferred pattern for a type you own).
Typed object-type ids
ObjectTypeId is keyof ObjectTypeMap | (string & {}) — an open interface any plugin can augment via TypeScript's module-augmentation pattern to get autocomplete/typo-checking on its own type ids, without core or any other plugin needing to know about them ahead of time:
declare module "@rifrocket/fabricjs-design-tool" {
interface ObjectTypeMap {
"sticky-note": StickyNoteConfig;
}
}
Several shipped plugins (shapes-basic, image, qrcode) do exactly this in their own objectTypeMap.ts file — check one of those for a complete example.