@voiden/sdk

Official SDK for building Voiden extensions with support for both UI (renderer process) and Electron (main process).
What is Voiden?
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.
Features
- 🎨 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
Prerequisites
- 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)
Installation
npm install @voiden/sdk
yarn add @voiden/sdk
Installing Peer Dependencies
For UI extensions, you'll also need to install the required peer dependencies:
npm install react react-dom @tiptap/core @tiptap/pm @tiptap/react
Quick Start
Create 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!');
},
});
}
}
Architecture
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
UI Extension Example
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() {
this.registerBlock({
name: 'my-custom-block',
label: 'My Custom Block',
node: MyCustomNode,
icon: 'box',
});
this.registerSlashCommand({
name: 'insert-custom',
label: 'Custom Block',
slash: '/custom',
description: 'Insert a custom block',
action: (editor) => {
editor.commands.insertContent({ type: 'my-custom-block' });
},
});
this.registerSidebar('right', {
id: 'my-sidebar',
title: 'My Sidebar',
component: MySidebarComponent,
});
}
}
Electron Extension Example
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() {
this.registerIPCHandler('my-channel', async (data) => {
const result = await this.processData(data);
return { success: true, result };
});
this.registerProtocol({
scheme: 'myapp',
handler: async (url) => {
return { data: 'Custom protocol response' };
},
});
this.watchFileSystem({
path: '/path/to/watch',
onChange: (event, path) => {
},
});
}
private async processData(data: any) {
return data;
}
}
Full Extension (UI + Electron)
Many extensions need both UI and Electron components:
import { MyUIExtension } from './ui';
import { MyElectronExtension } from './electron';
export const ui = new MyUIExtension();
export const electron = new MyElectronExtension();
API Reference
UI Extension APIs
Core Methods
registerBlock(block: BlockDefinition) - Register a Tiptap node as a custom block
registerSlashCommand(command: SlashCommandDefinition) - Register a slash command
registerSlashGroup(group: SlashCommandGroup) - Register a group of slash commands
registerSidebar(side: 'left' | 'right', tab: TabDefinition) - Register a sidebar tab
registerPanel(panel: PanelDefinition) - Register a bottom panel
showModal(modal: ModalDefinition) - Show a modal dialog
showToast(message: string, type?: 'info' | 'success' | 'error') - Show a toast notification
getEditor(type?: 'main' | 'note') - Get the active editor instance
Advanced APIs
storage - Extension-scoped persistent storage
pipeline - Hook into request/response lifecycle
environment - Secure environment variable access
helpers - Access helpers from other extensions
Electron Extension APIs
Core Methods
registerIPCHandler(channel: string, handler: Function) - Register an IPC message handler
sendToRenderer(channel: string, data: any) - Send message to renderer process
registerMenuItem(menu: string, item: MenuItemDefinition) - Register a menu item
registerProtocol(handler: ProtocolHandler) - Register custom URL protocol handler
watchFileSystem(watcher: FSWatcherDefinition) - Watch file system changes
spawn(command: string, args: string[]) - Spawn a child process
exec(command: string) - Execute a shell command
Advanced APIs
storage - Extension-scoped persistent storage
app - Access to Electron app instance
mainWindow - Access to main BrowserWindow
Shared Types
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.
Examples
The examples directory contains complete, working examples:
- Hello World - Basic slash command and toast notifications
- More examples coming soon!
Using Examples
Each example is a standalone project you can use as a template:
cd examples/hello-world
npm install
npm run build
Then copy the built extension to your Voiden extensions directory.
Voiden Runner (Headless)
The Voiden Runner allows extensions to execute headlessly in plain Node.js environments (CLI/CI). This is essential for automation and integration testing where no UI or Electron context exists.
For detailed information on how attributes are normalized, see the Block Schema Guide.
Runner Example (runner.ts)
import type { RunnerFactory, RunnerContext } from '@voiden/sdk/runner';
const myRunner: RunnerFactory = (context: RunnerContext) => {
return {
async onload() {
context.registerBlockSchema({
name: 'my-custom-block',
attrs: { url: { default: 'https://api.example.com' } }
});
context.onBuildRequest((request, blocks) => {
const myBlock = blocks.find(b => b.type === 'my-custom-block');
return myBlock ? { ...request, url: myBlock.attrs?.url, method: 'POST' } : request;
});
context.onProcessResponse((response, blocks, request) => {
if (response.status === 200) {
context.report.add({ type: 'assertion', passed: true, message: 'Status 200 OK' });
}
});
}
};
};
export default myRunner;
Runner API Reference
Core Methods
registerBlockSchema(def: BlockSchemaDef) - Define block attributes and defaults for normalization
onBuildRequest(handler: RunnerRequestHandler) - Transform document blocks into a request state
onProcessResponse(handler: RunnerResponseHandler) - Evaluate results and emit report entries
pipeline.registerHook(stage, handler, priority?) - Register granular pipeline hooks (e.g., pre-send, post-processing)
report.add(entry: CliReportEntry) - Emit logs or assertions to the CLI/CI output
Context Properties
env - Flat map of resolved environment variables
verbose - Boolean flag indicating if verbose logging is enabled
Troubleshooting
Common Issues
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.json includes "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
await when sending IPC messages that expect responses
Build errors
- Run
npm run typecheck to see detailed TypeScript errors
- Ensure you're using Node.js 20.x or higher
- Clear
dist folder and rebuild: rm -rf dist && npm run build
Getting Help
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development
git clone https://github.com/VoidenHQ/sdk.git
cd sdk
npm install
npm run build
npm run dev
npm run typecheck
Security
Found a security vulnerability? Please see our Security Policy for reporting instructions.
License
MIT - see LICENSE file for details.
Links
Built with ❤️ by the Voiden team