@voiden/sdk
May 14, 2026 ยท View on GitHub
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
# or
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() {
// 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,
});
}
}
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() {
// 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;
}
}
Full Extension (UI + Electron)
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();
API Reference
UI Extension APIs
Core Methods
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
Advanced APIs
storage- Extension-scoped persistent storagepipeline- Hook into request/response lifecycleenvironment- Secure environment variable accesshelpers- Access helpers from other extensions
Electron Extension APIs
Core Methods
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
Advanced APIs
storage- Extension-scoped persistent storageapp- Access to Electron app instancemainWindow- 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() {
// Define attribute defaults for headless normalization
context.registerBlockSchema({
name: 'my-custom-block',
attrs: { url: { default: 'https://api.example.com' } }
});
// Build requests from document blocks
context.onBuildRequest((request, blocks) => {
const myBlock = blocks.find(b => b.type === 'my-custom-block');
return myBlock ? { ...request, url: myBlock.attrs?.url, method: 'POST' } : request;
});
// Process responses (assertions/logging)
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 normalizationonBuildRequest(handler: RunnerRequestHandler)- Transform document blocks into a request stateonProcessResponse(handler: RunnerResponseHandler)- Evaluate results and emit report entriespipeline.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 variablesverbose- 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.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
Getting Help
- Check existing issues
- Read the examples for working code
- Open a new issue with a minimal reproduction
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development
# 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 typecheck
Security
Found a security vulnerability? Please see our Security Policy for reporting instructions.
License
MIT - see LICENSE file for details.
Links
Community
Built with โค๏ธ by the Voiden team