Official SDK for building Voiden extensions with support for both UI (renderer process) and Electron (main process).
Voiden is an extensible, privacy-focused editor built on Electron and Tiptap. The Voiden SDK enables developers to create powerful extensions that enhance the editor's functionality, from custom blocks and slash commands to system-level integrations.
- 🎨 UI Extensions - Build custom editor blocks, slash commands, sidebars, and panels
- ⚡ Electron Extensions - Access native APIs, file system, and system integrations
- 🔄 Cross-Process Communication - Seamless IPC between UI and Electron extensions
- 💾 Storage API - Extension-scoped persistent storage
- 🔌 Pipeline System - Hook into request/response lifecycle
- 🌍 Environment API - Secure access to environment variables
- 📦 Helper System - Cross-extension communication and shared functionality
- 🔒 Type-Safe - Full TypeScript support with complete type definitions
- Node.js 20.x or higher
- npm 10.x or higher (or yarn/pnpm)
- TypeScript 5.x knowledge recommended
- React 18.x (for UI extensions)
- Tiptap 2.x (for custom editor blocks)
npm install @voiden/sdk
# or
yarn add @voiden/sdkFor UI extensions, you'll also need to install the required peer dependencies:
npm install react react-dom @tiptap/core @tiptap/pm @tiptap/reactCreate your first extension in minutes:
import { UIExtension } from '@voiden/sdk/ui';
export class MyExtension extends UIExtension {
name = 'my-first-extension';
version = '1.0.0';
description = 'My first Voiden extension';
async onLoad() {
this.registerSlashCommand({
name: 'greet',
label: 'Say Hello',
slash: '/hello',
description: 'Insert a greeting',
action: (editor) => {
editor.commands.insertContent('Hello from my extension!');
},
});
}
}The SDK is divided into three parts:
- UI Extensions (
@voiden/sdk/ui) - Run in the renderer process, handle editor blocks, UI components - Electron Extensions (
@voiden/sdk/electron) - Run in the main process, handle IPC, file system, native APIs - Shared (
@voiden/sdk/shared) - Shared types and utilities
import { UIExtension } from '@voiden/sdk/ui';
import { MyCustomNode } from './nodes/MyCustomNode';
export class MyUIExtension extends UIExtension {
name = 'my-ui-extension';
version = '1.0.0';
description = 'My awesome UI extension';
async onLoad() {
// Register a custom block
this.registerBlock({
name: 'my-custom-block',
label: 'My Custom Block',
node: MyCustomNode,
icon: 'box',
});
// Register a slash command
this.registerSlashCommand({
name: 'insert-custom',
label: 'Custom Block',
slash: '/custom',
description: 'Insert a custom block',
action: (editor) => {
editor.commands.insertContent({ type: 'my-custom-block' });
},
});
// Register a sidebar
this.registerSidebar('right', {
id: 'my-sidebar',
title: 'My Sidebar',
component: MySidebarComponent,
});
}
}import { ElectronExtension } from '@voiden/sdk/electron';
export class MyElectronExtension extends ElectronExtension {
name = 'my-electron-extension';
version = '1.0.0';
description = 'My awesome Electron extension';
async onLoad() {
// Register IPC handler
this.registerIPCHandler('my-channel', async (data) => {
const result = await this.processData(data);
return { success: true, result };
});
// Register custom protocol
this.registerProtocol({
scheme: 'myapp',
handler: async (url) => {
return { data: 'Custom protocol response' };
},
});
// Watch file system
this.watchFileSystem({
path: '/path/to/watch',
onChange: (event, path) => {
},
});
}
private async processData(data: any) {
// Custom processing logic
return data;
}
}Many extensions need both UI and Electron components:
// index.ts
import { MyUIExtension } from './ui';
import { MyElectronExtension } from './electron';
export const ui = new MyUIExtension();
export const electron = new MyElectronExtension();registerBlock(block: BlockDefinition)- Register a Tiptap node as a custom blockregisterSlashCommand(command: SlashCommandDefinition)- Register a slash commandregisterSlashGroup(group: SlashCommandGroup)- Register a group of slash commandsregisterSidebar(side: 'left' | 'right', tab: TabDefinition)- Register a sidebar tabregisterPanel(panel: PanelDefinition)- Register a bottom panelshowModal(modal: ModalDefinition)- Show a modal dialogshowToast(message: string, type?: 'info' | 'success' | 'error')- Show a toast notificationgetEditor(type?: 'main' | 'note')- Get the active editor instance
storage- Extension-scoped persistent storagepipeline- Hook into request/response lifecycleenvironment- Secure environment variable accesshelpers- Access helpers from other extensions
registerIPCHandler(channel: string, handler: Function)- Register an IPC message handlersendToRenderer(channel: string, data: any)- Send message to renderer processregisterMenuItem(menu: string, item: MenuItemDefinition)- Register a menu itemregisterProtocol(handler: ProtocolHandler)- Register custom URL protocol handlerwatchFileSystem(watcher: FSWatcherDefinition)- Watch file system changesspawn(command: string, args: string[])- Spawn a child processexec(command: string)- Execute a shell command
storage- Extension-scoped persistent storageapp- Access to Electron app instancemainWindow- Access to main BrowserWindow
All shared types and utilities are available from @voiden/sdk/shared.
For complete API documentation with detailed examples, see the TypeScript definitions or check out our examples.
The examples directory contains complete, working examples:
- Hello World - Basic slash command and toast notifications
- More examples coming soon!
Each example is a standalone project you can use as a template:
cd examples/hello-world
npm install
npm run buildThen copy the built extension to your Voiden extensions directory.
TypeScript errors about missing types
- Ensure all peer dependencies are installed:
npm install react react-dom @tiptap/core @tiptap/pm @tiptap/react - Check that your
tsconfig.jsonincludes"moduleResolution": "node"
Extension not loading in Voiden
- Verify your build output is in the correct format (ESM)
- Check the browser console for error messages
- Ensure your extension class is properly exported
IPC handlers not receiving messages
- Verify the channel name matches between UI and Electron extensions
- Check that the Electron extension is loaded before sending messages
- Use
awaitwhen sending IPC messages that expect responses
Build errors
- Run
npm run typecheckto see detailed TypeScript errors - Ensure you're using Node.js 20.x or higher
- Clear
distfolder and rebuild:rm -rf dist && npm run build
- Check existing issues
- Read the examples for working code
- Open a new issue with a minimal reproduction
We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/VoidenHQ/sdk.git
cd sdk
# Install dependencies
npm install
# Build the SDK
npm run build
# Watch mode for development
npm run dev
# Type checking
npm run typecheckFound a security vulnerability? Please see our Security Policy for reporting instructions.
MIT - see LICENSE file for details.
Built with ❤️ by the Voiden team