Status: Planned feature - External plugin installation is not yet implemented. This guide is for future reference.
This guide covers how to build, test, publish, install, and uninstall external plugins for Voiden.
- Building an External Plugin
- Testing an External Plugin
- Publishing an External Plugin
- Installing an External Plugin
- Uninstalling an External Plugin
Create a new npm project:
mkdir my-voiden-extension
cd my-voiden-extension
npm init -yInstall the SDK as a dev dependency:
npm install --save-dev @voiden/sdk
npm install --save-dev typescriptmy-voiden-extension/
├── src/
│ ├── manifest.json # Required metadata
│ ├── extension.ts # Your extension implementation
│ └── index.ts # Entry point
├── dist/
│ ├── manifest.json # Copied from src
│ └── main.js # Compiled output
├── package.json
└── tsconfig.json
Create src/manifest.json:
{
"id": "my-extension",
"type": "community",
"name": "My Extension",
"description": "Does something cool",
"version": "1.0.0",
"author": "Your Name",
"enabled": true,
"priority": 50,
"capabilities": {
"blocks": {
"owns": ["my-block"],
"allowExtensions": true
}
},
"dependencies": {
"sdk": "^0.1.0"
}
}Create src/extension.ts:
import { UIExtension } from '@voiden/sdk/ui';
export class MyExtension extends UIExtension {
name = 'my-extension';
version = '1.0.0';
async onLoad(): Promise<void> {
// Register your blocks, commands, etc.
this.registerSlashCommand({
name: 'my-command',
label: 'My Command',
description: 'Does something',
slash: '/mycommand',
action: (editor) => {
// Your logic
}
});
}
}Create src/index.ts:
import { MyExtension } from './extension';
export default MyExtension;Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Update package.json:
{
"name": "my-voiden-extension",
"version": "1.0.0",
"main": "dist/main.js",
"scripts": {
"build": "tsc && cp src/manifest.json dist/manifest.json"
},
"devDependencies": {
"@voiden/sdk": "^0.1.0",
"typescript": "^5.0.0"
}
}npm run buildThis creates dist/main.js and dist/manifest.json.
Manually copy your built extension to the user data directory:
# Find your userData directory
# macOS: ~/Library/Application Support/Voiden
# Linux: ~/.config/Voiden
# Windows: %APPDATA%/Voiden
# Create extension directory
mkdir -p ~/.config/Voiden/extensions/my-extension
# Copy built files
cp dist/main.js ~/.config/Voiden/extensions/my-extension/
cp dist/manifest.json ~/.config/Voiden/extensions/my-extension/Then update ~/.config/Voiden/extensions/installed.json:
[
{
"id": "my-extension",
"type": "community",
"name": "My Extension",
"version": "1.0.0",
"installedPath": "/Users/you/.config/Voiden/extensions/my-extension",
"enabled": true
}
]Restart Voiden to load the extension.
You could modify the extension manager to support local development paths:
// In apps/electron/src/main/extension/extensionManager.ts
async loadLocalDevExtension(localPath: string) {
const manifestPath = path.join(localPath, 'dist', 'manifest.json');
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
this.store.extensions.push({
...manifest,
type: 'community',
installedPath: path.join(localPath, 'dist'),
enabled: true,
});
}Based on the installation code at apps/electron/src/main/extension/extensionManager.ts:139, extensions are fetched from GitHub repositories.
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/yourusername/my-voiden-extension.git
git push -u origin maingit tag v1.0.0
git push origin v1.0.0On GitHub, create a release with:
- Tag:
v1.0.0 - Attach
dist/manifest.jsonanddist/main.jsas release assets
The installer expects this structure in your GitHub repo:
https://github.com/yourusername/my-voiden-extension
└── releases/v1.0.0/
├── manifest.json
└── main.js
The installation code fetches these files via GitHub API or raw URLs.
Looking at the IPC handlers at apps/electron/src/main/state.ts:147-151, users would:
- Open Extension Manager UI in Voiden
- Search for extensions (possibly from a registry/marketplace)
- Click "Install" on your extension
- Provide repo URL:
https://github.com/yourusername/my-voiden-extension - Select version:
v1.0.0
The app calls:
await window.electron.extensions.install({
id: 'my-extension',
repo: 'https://github.com/yourusername/my-voiden-extension',
version: 'v1.0.0'
});From apps/electron/src/main/extension/extensionManager.ts:139-167:
async installCommunityExtension(extension: ExtensionData) {
// 1. Fetch files from GitHub
const { manifest, main } = await installer.getExtensionFiles(
extension.repo,
extension.version
);
// 2. Create directory in ~/.userData/extensions/
const installPath = path.join(communityDir, extension.id);
await fs.mkdir(installPath, { recursive: true });
// 3. Write files
await fs.writeFile(path.join(installPath, "manifest.json"), manifest);
await fs.writeFile(path.join(installPath, "main.js"), main);
// 4. Register in state
this.store.extensions.push(extension);
// 5. Persist to installed.json
await this.saveInstalledCommunityExtensions();
return extension;
}- Open Extension Manager
- Find your extension
- Click "Uninstall"
Calls:
await window.electron.extensions.uninstall('my-extension');From apps/electron/src/main/extension/extensionManager.ts:169-182:
async uninstallCommunityExtension(extensionId: string) {
const ext = this.store.extensions.find(e => e.id === extensionId);
if (ext?.installedPath) {
// 1. Delete from filesystem
await fs.rm(ext.installedPath, { recursive: true, force: true });
// 2. Remove from state
this.store.extensions = this.store.extensions.filter(
e => e.id !== extensionId
);
// 3. Update installed.json
await this.saveInstalledCommunityExtensions();
}
}| Step | Command/Action |
|---|---|
| Build | npm run build → creates dist/main.js + dist/manifest.json |
| Test Locally | Copy to ~/.userData/extensions/my-extension/, update installed.json, restart app |
| Publish | Push to GitHub, create release with dist files |
| Install | Use Extension Manager UI or window.electron.extensions.install() |
| Uninstall | Use Extension Manager UI or window.electron.extensions.uninstall() |
apps/electron/src/main/extension/extensionManager.ts:139-182- Install/uninstall logicapps/electron/src/main/extension/installer.ts- GitHub fetchingapps/electron/src/preload/api/misc.ts- Exposed APIpackages/sdk/src/ui/Extension.ts- UIExtension base classdocs/extension-architecture.md- Complete architecture designdocs/extensions/HOW_TO_ADD.md- Quick start for core extensions
For working examples of extensions, see:
core-extensions/src/voiden-rest-api/- Complex extension with blocks and pipeline hookscore-extensions/src/md-preview/- Simple extension with editor actionscore-extensions/src/simple-assertions/- Response processing extensioncore-extensions/src/voiden-faker/- Pre-send pipeline hooks
For detailed SDK API documentation, see:
packages/sdk/README.md- SDK usage guidepackages/sdk/SDK_EXTENSIONS_PLAN.md- Future SDK enhancements