Plugin Development
Build custom plugins for Readied — commands, UI components, editor extensions, and more
Plugin Development
Readied supports a plugin system that lets you extend the editor with custom functionality. Plugins can add commands, UI components, editor extensions, and more.
Quick Start
Scaffold a new plugin
npx readied-plugin init "My Plugin"
cd my-plugin
npm installThis creates a plugin project with TypeScript, the plugin API types, and a starter template.
Readied ships with 8 built-in plugins you can use as reference. Check the apps/desktop/src/renderer/plugins/ directory for real-world examples.
Write your plugin
Edit src/index.ts:
import type { PluginManifest } from '@readied/plugin-api';
export const plugin: PluginManifest = {
id: 'my-plugin',
name: 'My Plugin',
version: '0.1.0',
description: 'Does something useful',
activate(context) {
// Register a command in the command palette
const unregister = context.registerCommand(
{
id: 'hello',
name: 'Say Hello',
icon: 'Smile',
},
() => {
context.log.info('Hello from My Plugin!');
return true;
}
);
// Clean up when the plugin is deactivated
return {
dispose() {
unregister();
},
};
},
};Build and install
npm run buildCopy the output to your Readied plugins directory:
cp -r dist/ ~/Library/Application\ Support/Readied/plugins/my-plugin/xcopy /E dist\ %APPDATA%\Readied\plugins\my-plugin\cp -r dist/ ~/.config/Readied/plugins/my-plugin/Reload Readied
The plugin will be discovered and loaded automatically on next launch. Toggle it in Settings > Plugins.
Plugin Contract
The five rules
Readied plugins must follow these rules to maintain the offline-first, markdown-portable principle.
- Removable without breaking
.mdfiles — Your plugin can be uninstalled and all notes remain valid markdown. - No new syntax — Don't invent markdown extensions that other editors can't read.
- No automatic content mutation — Never modify the user's markdown without explicit action.
- Not required to interpret text — Notes must be readable without your plugin.
- No inter-note dependencies — Don't create relationships that break if notes are moved or deleted.
Plugin Lifecycle
load → activate(context) → [user interacts] → dispose()- Load — Readied discovers your plugin and reads
manifest.json - Activate — Your
activate()function receives aPluginContextwith the full API - Running — Your plugin responds to commands, events, and editor changes
- Dispose — When disabled or on app exit, your
dispose()function cleans up
Always clean up. Return a { dispose() } object from activate(). Unregister all commands, remove all UI components, and clear any subscriptions.