# Plugins
The plugin system lets you extend the editor with custom blocks, categories, toolbar actions, sidebar panels, and property editors — without modifying the source.
## Creating a Plugin
A plugin is a function that receives a `PluginContext`:
```ts
import type { Plugin } from '@lab2view/vue-email-editor'
const myPlugin: Plugin = (ctx) => {
// Use ctx to register extensions
}
```
### Register It
```vue
```
Plugins are initialized **before** the `editor:ready` event fires.
## Adding Custom Blocks
```ts
import type { Plugin } from '@lab2view/vue-email-editor'
import { createText, createImage, createSection, createColumn } from '@lab2view/vue-email-editor'
const brandPlugin: Plugin = (ctx) => {
// Simple block
ctx.registerBlock({
id: 'brand-signature',
label: 'Signature',
category: 'content',
icon: 'PenTool',
factory: () => createText(`
John Doe
Product Manager at Acme Corp
john@acme.com
`),
})
// Complex block (section with multiple columns)
ctx.registerBlock({
id: 'brand-header',
label: 'Brand Header',
category: 'composite',
icon: 'Layout',
factory: () => {
const logo = createImage({
src: 'https://acme.com/logo.png',
alt: 'Acme Corp',
width: '150px',
})
const col = createColumn([logo])
return createSection([col], {
'background-color': '#1a1a2e',
padding: '20px 0',
})
},
})
}
```
## Adding Block Categories
```ts
const myPlugin: Plugin = (ctx) => {
ctx.registerBlockCategory({
id: 'brand',
label: 'Brand',
icon: 'Building',
order: 5, // Lower = displayed first
})
ctx.registerBlock({
id: 'brand-banner',
label: 'Brand Banner',
category: 'brand', // Uses our new category
icon: 'Flag',
factory: () => createText('Welcome to Acme
'),
})
}
```
## Adding Toolbar Actions
```ts
const savePlugin: Plugin = (ctx) => {
ctx.registerToolbarAction({
id: 'save',
label: 'Save',
icon: 'Save',
position: 'right',
order: 10,
handler: () => {
console.log('Saving...')
},
})
ctx.registerToolbarAction({
id: 'preview',
label: 'Preview',
icon: 'Eye',
position: 'right',
order: 20,
handler: () => {
console.log('Opening preview...')
},
})
}
```
## Listening to Events
Plugins receive the full event system:
```ts
const analyticsPlugin: Plugin = (ctx) => {
ctx.on('editor:change', ({ document }) => {
// Track changes
analytics.track('email_edited', {
nodeCount: countNodes(document.body),
})
})
ctx.on('node:deleted', ({ nodeId }) => {
console.log(`Node ${nodeId} deleted`)
})
ctx.on('block:dropped', ({ blockId, parentId }) => {
console.log(`Block ${blockId} dropped into ${parentId}`)
})
}
```
## Async Plugins
Plugins can be async (e.g., to fetch remote block definitions):
```ts
const remoteBlocksPlugin: Plugin = async (ctx) => {
const response = await fetch('/api/custom-blocks')
const blocks = await response.json()
for (const block of blocks) {
ctx.registerBlock({
id: block.id,
label: block.name,
category: 'custom',
icon: block.icon || 'Box',
factory: () => createText(block.defaultHtml),
})
}
}
```
## Plugin Context API
| Method | Description |
|--------|-------------|
| `registerBlock(block)` | Add a block to the blocks panel |
| `registerBlockCategory(category)` | Add a new category tab |
| `registerPropertyEditor(type, component)` | Override a property editor for a type |
| `registerToolbarAction(action)` | Add a button to the toolbar |
| `registerSidebarPanel(panel)` | Add a sidebar tab |
| `on(event, handler)` | Listen to editor events |
| `off(event, handler)` | Remove an event listener |
| `labels` | Reactive reference to current `EditorLabels` |
## Multiple Plugins
```vue
```
Plugins are executed in order. Duplicate block/category IDs are silently ignored (first registration wins).