How to Register a Custom Canvas Module in the Instatic Visual Editor

You register a custom canvas module by calling api.editor.canvas.registerOverlay() from your plugin's bootstrap function, passing a CanvasOverlay configuration object that implements lifecycle hooks such as onMount, onUnmount, and onUpdate.

In the CoreBunch/Instatic visual editor, canvas extensions are implemented through the Plugin SDK, which executes plugin code inside a QuickJS sandbox. To register a custom canvas module, you must define an overlay conforming to the CanvasOverlay interface declared in src/core/plugin-sdk/types/canvasOverlays.ts, then invoke the registration API during the plugin bootstrap phase.

Understanding the Canvas Overlay Architecture

The Instatic Plugin SDK exposes the editor surface through an api object injected into the QuickJS sandbox at runtime. Canvas modules are registered as overlays that render on top of the visual editor canvas and participate in the editor's lifecycle events.

According to the source code in src/core/plugin-sdk/types/canvasOverlays.ts, every overlay must implement the CanvasOverlay interface, which requires a unique identifier and optional lifecycle hooks. The Redux slice defined in src/admin/pages/site/store/slices/canvasSlice.ts manages the state for active overlays, automatically dispatching mount and unmount events when the editor view changes.

Step 1: Define the Canvas Overlay

Create a TypeScript module that exports an object conforming to the CanvasOverlay type. This definition specifies how your module initializes, updates, and cleans up when attached to the canvas DOM.

// src/my-plugin/canvas/myOverlay.ts
import type { CanvasOverlay } from '@core/plugin-sdk/types/canvasOverlays';

export const myOverlay: CanvasOverlay = {
  // Unique identifier used by the editor
  id: 'my-plugin:exampleOverlay',

  // Called once when the overlay is mounted to the canvas
  onMount(rootElement) {
    const button = document.createElement('button');
    button.textContent = 'Click Me';
    button.onclick = () => alert('Overlay button clicked!');
    rootElement.appendChild(button);
  },

  // Optional: clean-up when the overlay is removed
  onUnmount(rootElement) {
    rootElement.innerHTML = '';
  },

  // Optional: react to canvas redraws or mode changes
  onUpdate(canvasState) {
    // adjust UI based on canvasState if needed
  },
};

The onMount hook receives the rootElement where you can inject DOM nodes, while onUpdate allows you to react to canvas state changes such as zoom level or selected elements.

Step 2: Export the Bootstrap Function

Every Instatic plugin must export a default bootstrap function that receives the api object. This is where you register your canvas module with the editor.

// src/my-plugin/bootstrap.ts
import { myOverlay } from './canvas/myOverlay';

export default function (api: any) {
  // The editor surface is exposed under `api.editor.canvas`
  api.editor.canvas.registerOverlay(myOverlay);
}

When the server loads your plugin, it executes this bootstrap function inside the sandbox, making your overlay available to the canvas lifecycle manager.

Step 3: Configure the Plugin Manifest

The plugin.json manifest must declare the canvas permission and specify the entry point for the bootstrap script.

{
  "name": "my-plugin",
  "apiVersion": "2",
  "manifestVersion": "1",
  "permissions": ["canvas"],
  "entry": "bootstrap.js"
}

Without the "permissions": ["canvas"] declaration, the api.editor.canvas namespace will not be accessible to your plugin, and the registration will fail.

Step 4: Build and Install

Bundle your plugin using the repository's build pipeline. The helper utilities in src/core/plugin-sdk/builders/canvasOverlay.ts provide additional construction helpers for complex overlay definitions, though they are optional for basic implementations.

Run the build command:

bun run build

# or

bun run scripts:sync-plugin-bootstrap

After building, install the generated .zip file through the Instatic Admin UI under Plugins → Install. The overlay will mount automatically when the visual editor loads.

Lifecycle and State Management

Once registered, your canvas module is managed by the Redux slice defined in src/admin/pages/site/store/slices/canvasSlice.ts. This slice tracks active overlays and coordinates their mounting and unmounting as the user navigates between pages or editor modes.

The overlay persists for the duration of the editor session unless explicitly removed. When the editor view changes, the onUnmount hook fires, allowing you to clean up event listeners or DOM elements to prevent memory leaks.

Summary

  • Register your module via api.editor.canvas.registerOverlay() inside the plugin bootstrap function.
  • Define the overlay configuration using the CanvasOverlay interface from src/core/plugin-sdk/types/canvasOverlays.ts.
  • Implement lifecycle hooks (onMount, onUnmount, onUpdate) to control how your module interacts with the canvas DOM.
  • Declare the canvas permission in plugin.json to access the editor surface API.
  • Build and install the plugin through the admin interface; the overlay mounts automatically when the editor initializes.

Frequently Asked Questions

What permissions are required to register a canvas module?

Your plugin must include "permissions": ["canvas"] in the plugin.json manifest. Without this permission, the api.editor.canvas namespace is undefined, and calling registerOverlay() will throw a runtime error.

Can I register multiple canvas overlays from a single plugin?

Yes. You can call api.editor.canvas.registerOverlay() multiple times within the same bootstrap function, passing different CanvasOverlay definitions with unique IDs. Each overlay operates independently with its own lifecycle hooks.

How does the QuickJS sandbox affect DOM access?

The bootstrap code runs inside a QuickJS sandbox for security, but the onMount hook receives a standard DOM rootElement where you can use standard browser APIs like document.createElement() and appendChild(). The sandbox restricts access to the global window object in the bootstrap context, but DOM manipulation within the provided root element works normally.

Where is the canvas overlay state managed?

Active canvas overlays are tracked in the Redux store slice located at src/admin/pages/site/store/slices/canvasSlice.ts. This slice dispatches actions when overlays mount, update, or unmount, ensuring proper cleanup when users switch between editor views.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →