Skip to main content

Plugin Development Overview

Voiden's plugin system lets you extend the application with new features, UI components, editor blocks, and integrations — all without touching the core codebase. Whether you want to add a simple slash command or build a full protocol handler, plugins give you the tools to make it happen.


What Can Plugins Do?

Plugins have access to a rich set of APIs through the PluginContext object:

CapabilityDescriptionExample
Slash CommandsAdd commands to the / menu in the editor/assertions, /hello
Custom Editor BlocksCreate new Tiptap node types rendered in the documentRequest blocks, table blocks
Sidebar TabsAdd tabs to the left or right sidebarAPI catalog browser
Panels & TabsRegister custom panels in the main content areaExplorer panels
Status Bar ItemsAdd buttons to the bottom status barQuick-launch buttons
Pipeline HooksIntercept and modify requests before sending or responses after receivingFake data injection, assertion testing
Paste HandlersHandle custom clipboard content (e.g., cURL commands)cURL import on paste
HelpersExpose utility functions for other plugins to useData parsers, formatters
CodeMirror ExtensionsExtend the code editor with autocomplete, linting, etc.Faker.js autocomplete
Command PaletteAdd entries to the ⌘⇧P command paletteQuick-run commands
Context MenusInject items into right-click context menusTab-level actions

Plugin Architecture

Every Voiden plugin scaffold produces this structure:

my-plugin/
├── src/
│ ├── plugin.ts ← your entry point — edit this
│ └── skill.md ← AI skill description
├── manifest.json ← plugin identity, permissions, capabilities
├── changelog.json ← release history
├── package.json
├── tsconfig.json
├── build.mjs ← Vite build (renderer bundle)
├── build-main.mjs ← esbuild build (main-process bundle)
├── zip.mjs ← packages dist/ into an installable .zip
└── .github/
└── workflows/
└── release.yml ← GitHub Actions: build & publish on git tag

The final deliverable is a ZIP file containing at minimum {id}.js and manifest.json. Users install it in Voiden via Extensions → ⋯ → Install from file.

Use the scaffolder

@voiden/create-plugin generates this entire structure in one command — no manual config files needed.

npm create @voiden/plugin my-plugin

The Plugin Function

A plugin is a factory function that receives a CorePluginContext and returns { onload, onunload, metadata }:

import type { CorePluginContext } from '@voiden/sdk/ui';
import manifest from '../manifest.json';

export default function createMyPlugin(context: CorePluginContext) {
return {
onload: async () => {
// Register slash commands, blocks, sidebar tabs, hooks, etc.
},

onunload: async () => {
// Cancel subscriptions and clean up
},

metadata: manifest,
};
}
HookPurpose
onloadCalled once when the plugin activates. Register everything here
onunloadCalled on disable or app close. Always cancel subscriptions made in onload
metadataPass manifest directly — Voiden uses it for the Extensions browser display

Plugin Lifecycle

npm run build && npm run zip


User installs via Extensions → ⋯ → Install from file


Voiden validates the ZIP (manifest.json + {id}.js)


Plugin extracted to extensions directory


Extension Registry loads manifest metadata


onload() is called — your features register


Plugin is active


onunload() is called on disable or app close

The Manifest

manifest.json is the source of truth for your plugin. Voiden reads it when loading and the Extensions browser displays it to users:

{
"id": "my-plugin",
"name": "My Plugin",
"description": "What this plugin does",
"version": "1.0.0",
"voidenVersion": ">=2.0.0",
"author": "Your Name",
"icon": "Globe",
"type": "community",
"priority": 30,
"permissions": [],
"capabilities": {}
}
capabilities are auto-populated

Do not fill in capabilities manually. The build.mjs script scans your compiled bundle at build time and fills it in automatically.

See the Manifest Reference for the full schema.


How Bundling Works

build.mjs uses Vite to compile your plugin entry point into a single ESM file at dist/{id}.js. Host-provided packages like react, @tiptap/core, and lucide-react are not bundled — instead, the build emits inline shim code that reads from window.__voiden_shims__ at runtime. Voiden injects these shims before loading any plugin.

This keeps plugin bundles small (typically 5–15 kB) and ensures a single React instance across all plugins.


Prerequisites

RequirementVersion
Node.js18 or higher
zip on PATHPre-installed on macOS/Linux. On Windows, use WSL or 7-Zip CLI

Basic familiarity with TypeScript and React is helpful. You should also know what blocks, slash commands, and panels are from using Voiden — the APIs map directly to what you see in the app.


Where to Go Next

PageWhat You'll Learn
Getting StartedBuild your first plugin step by step
Plugin API ReferenceFull reference for the PluginContext API
Manifest ReferenceComplete manifest.json schema

Existing Plugins for Reference

The best way to learn is by studying existing plugins:

PluginComplexityGood For Learning
md-previewSimpleEditor actions, helper exposure
voiden-fakerMediumPipeline hooks, CodeMirror extensions, TipTap suggestions
simple-assertionsMediumCustom blocks, response processing, slash commands
voiden-rest-apiComplexFull block ownership, paste handlers, pipeline hooks

You can find these in the core-extensions/src/ directory of the Voiden source code.