Skip to main content
Canvas Editor provides a flexible plugin system that allows you to extend the editor’s functionality without modifying the core codebase.

Plugin Architecture

Plugins are functions that receive the editor instance and optional configuration, allowing them to:
  • Add new commands to the editor
  • Modify existing behavior
  • Register event listeners
  • Access the full editor API

Plugin Function Type

type PluginFunction<Options> = (editor: Editor, options?: Options) => void

Using Plugins

Plugins are registered using the editor.use() method:
import Editor from 'canvas-editor'
import { myPlugin } from './plugins/myPlugin'

const editor = new Editor(container, data, options)

// Use plugin without options
editor.use(myPlugin)

// Use plugin with options
editor.use(myPlugin, { 
  setting1: 'value',
  setting2: true 
})

Creating a Plugin

1

Define the Plugin Function

Create a function that accepts the editor instance and optional configuration:
import Editor from 'canvas-editor'

export interface MyPluginOptions {
  enabled?: boolean
  prefix?: string
}

export function myPlugin(editor: Editor, options?: MyPluginOptions) {
  const { enabled = true, prefix = '[Plugin]' } = options || {}
  
  if (!enabled) return
  
  // Plugin implementation here
}
2

Access Editor APIs

Use the editor instance to access commands, listeners, and other APIs:
export function myPlugin(editor: Editor, options?: MyPluginOptions) {
  // Access command API
  const command = editor.command
  
  // Access event listeners
  editor.listener.rangeStyleChange = (payload) => {
    console.log('Style changed:', payload)
  }
  
  // Access event bus
  editor.eventBus.on('rangeStyleChange', (payload) => {
    console.log('Range style changed:', payload)
  })
}
3

Extend Command API (Optional)

Add custom commands by extending the Command class:
import { Command } from 'canvas-editor'

type CommandWithCustom = Command & {
  executeCustomAction(value: string): void
}

export function myPlugin(editor: Editor) {
  const command = editor.command as CommandWithCustom
  
  command.executeCustomAction = (value: string) => {
    // Custom command implementation
    const elementList = [{ value, type: ElementType.TEXT }]
    command.executeInsertElementList(elementList)
  }
}

Real-World Plugin Examples

This plugin converts Markdown syntax to editor elements:
import Editor, {
  Command,
  ElementType,
  IElement,
  ListType,
  TitleLevel
} from 'canvas-editor'

export type CommandWithMarkdown = Command & {
  executeInsertMarkdown(markdown: string): void
}

function convertMarkdownToElement(markdown: string): IElement[] {
  const elementList: IElement[] = []
  const lines = markdown.trim().split('\n')
  
  for (let l = 0; l < lines.length; l++) {
    const line = lines[l]
    
    if (line.startsWith('#')) {
      const level = line.indexOf(' ')
      elementList.push({
        type: ElementType.TITLE,
        level: level as TitleLevel,
        value: '',
        valueList: [{ value: line.slice(level + 1) }]
      })
    } else if (line.startsWith('- ')) {
      elementList.push({
        type: ElementType.LIST,
        listType: ListType.UL,
        value: '',
        valueList: [{ value: line.slice(2) }]
      })
    } else if (/^\*\*(.*?)\*\*$/.test(line)) {
      const match = line.match(/^\*\*(.*?)\*\*$/)
      elementList.push({
        type: ElementType.TEXT,
        value: match![1],
        bold: true
      })
    } else {
      elementList.push({
        type: ElementType.TEXT,
        value: line
      })
    }
  }
  
  return elementList
}

export function markdownPlugin(editor: Editor) {
  const command = editor.command as CommandWithMarkdown
  
  command.executeInsertMarkdown = (markdown: string) => {
    const elementList = convertMarkdownToElement(markdown)
    command.executeInsertElementList(elementList)
  }
}
Usage:
editor.use(markdownPlugin)

// Now use the custom command
editor.command.executeInsertMarkdown('# Hello\n\n**Bold text**')

Plugin Best Practices

Each plugin should have a single, well-defined purpose. This makes them easier to maintain and reuse.
Always define TypeScript interfaces for plugin options and extended commands to improve developer experience.
If your plugin registers event listeners or creates DOM elements, provide a cleanup mechanism:
export function myPlugin(editor: Editor) {
  const handler = () => { /* ... */ }
  
  editor.eventBus.on('someEvent', handler)
  
  // Store reference for cleanup
  const originalDestroy = editor.destroy
  editor.destroy = () => {
    editor.eventBus.off('someEvent', handler)
    originalDestroy()
  }
}
Consider readonly mode, empty selections, and other edge cases in your plugin logic.
When extending the Command API, be careful not to break existing functionality. Always preserve the original behavior if possible.

Plugin API Reference

Editor Instance Properties

PropertyTypeDescription
commandCommandExecute editor commands
listenerListenerRegister event listeners
eventBusEventBusEvent bus for pub/sub
registerRegisterRegister shortcuts, menus, i18n
overrideOverrideOverride internal behavior
versionstringEditor version
useUsePluginRegister plugins
destroy() => voidCleanup and destroy editor
See the Commands API reference for available command methods.

Next Steps

Custom Shortcuts

Register custom keyboard shortcuts

Context Menus

Add custom context menu items

Override System

Override internal editor behavior

Event System

Listen to editor events

Build docs developers (and LLMs) love