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

> Learn to register a custom canvas module in the Instatic visual editor using api.editor.canvas.registerOverlay. Implement lifecycle hooks for seamless integration.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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.

```typescript
// 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.

```typescript
// 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`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) manifest must declare the **canvas** permission and specify the entry point for the bootstrap script.

```json
{
  "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`](https://github.com/CoreBunch/Instatic/blob/main/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:

```bash
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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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.