Plugin API Reference
Complete reference for developing Lokus plugins. The Plugin API provides safe, controlled access to editor, UI, filesystem, and core features.
Plugin Structure
Manifest File (plugin.json)
Every plugin requires a plugin.json
manifest file:
{
"name": "example-plugin",
"version": "1.0.0",
"description": "An example plugin for Lokus",
"author": "Your Name",
"main": "index.js",
"permissions": [
"read:files",
"write:files",
"ui:editor"
],
"dependencies": {
"some-package": "^1.0.0"
},
"keywords": ["productivity", "editor"],
"repository": "https://github.com/username/example-plugin",
"homepage": "https://example.com",
"license": "MIT"
}
Required Fields:
name
- Plugin identifier (alphanumeric, hyphens, underscores)version
- Semantic version (e.g., “1.0.0”)description
- Brief plugin descriptionauthor
- Plugin author namemain
- Entry point file pathpermissions
- Array of required permissions
Optional Fields:
dependencies
- npm package dependencieskeywords
- Search keywordsrepository
- Source code repository URLhomepage
- Plugin homepage URLlicense
- License identifier
Entry Point (index.js)
export default class ExamplePlugin {
/**
* Called when plugin is activated
* @param {PluginAPI} context - Plugin API instance
*/
activate(context) {
this.context = context;
// Add editor extension
context.addExtension({
name: 'customExtension',
// Extension implementation
});
// Add slash command
context.addSlashCommand({
name: 'example',
description: 'Example command',
handler: (editor) => {
// Command logic
}
});
// Register event listeners
context.on('file:save', this.onFileSave.bind(this));
}
/**
* Called when plugin is deactivated
*/
deactivate() {
// Cleanup resources
this.context.removeAllListeners();
}
onFileSave(data) {
console.log('File saved:', data);
}
}
PluginAPI Class
The PluginAPI
class is passed to your plugin’s activate()
method and provides all plugin capabilities.
Constructor
new PluginAPI(pluginId, editorAPI)
Parameters:
pluginId
(string) - Unique plugin identifiereditorAPI
(object) - Editor API instance
Editor API
addExtension()
Adds a custom TipTap extension to the editor.
context.addExtension(extension, options)
Parameters:
extension
(Extension) - TipTap extension instanceoptions
(object) - Extension options
Returns: string
- Extension ID
Example:
import { Node } from '@tiptap/core';
const customExtension = Node.create({
name: 'customNode',
group: 'block',
content: 'inline*',
parseHTML() {
return [{ tag: 'div.custom-node' }];
},
renderHTML() {
return ['div', { class: 'custom-node' }, 0];
}
});
const extensionId = context.addExtension(customExtension);
addSlashCommand()
Adds a slash command to the editor command palette.
context.addSlashCommand(command)
Parameters:
command
(object)name
(string) - Command namedescription
(string) - Command descriptionicon
(string) - Icon name or componenthandler
(function) - Command execution function
Returns: string
- Command ID
Example:
context.addSlashCommand({
name: 'insert-date',
description: 'Insert current date',
icon: 'calendar',
handler: (editor) => {
const date = new Date().toISOString().split('T')[0];
editor.commands.insertContent(date);
}
});
addToolbarButton()
Adds a button to the editor toolbar.
context.addToolbarButton(button)
Parameters:
button
(object)name
(string) - Button namelabel
(string) - Button labelicon
(string) - Icon nameaction
(function) - Click handlerisActive
(function) - Active state check
Returns: string
- Button ID
Example:
context.addToolbarButton({
name: 'highlight',
label: 'Highlight',
icon: 'marker',
action: (editor) => {
editor.chain().focus().toggleHighlight().run();
},
isActive: (editor) => {
return editor.isActive('highlight');
}
});
getEditorContent()
Gets the current editor content as HTML or markdown.
context.getEditorContent()
Returns: string
- Editor content
Example:
const content = context.getEditorContent();
console.log(content);
setEditorContent()
Sets the editor content.
context.setEditorContent(content)
Parameters:
content
(string) - Content to set (HTML or markdown)
Example:
context.setEditorContent('# New Content\n\nThis is new content.');
insertContent()
Inserts content at the current cursor position.
context.insertContent(content)
Parameters:
content
(string | object) - Content to insert
Example:
context.insertContent('Inserted text');
// Or insert HTML/nodes
context.insertContent({
type: 'paragraph',
content: [{ type: 'text', text: 'Inserted paragraph' }]
});
getSelection()
Gets the current editor selection.
context.getSelection()
Returns: object
- Selection object with from
, to
, and text
properties
Example:
const selection = context.getSelection();
console.log('Selected text:', selection.text);
console.log('Range:', selection.from, '-', selection.to);
UI API
registerPanel()
Registers a custom UI panel.
context.registerPanel(panel)
Parameters:
panel
(object)name
(string) - Panel nametitle
(string) - Panel titleicon
(string) - Panel iconcomponent
(Component) - React componentposition
(string) - ‘left’ | ‘right’ | ‘bottom’
Returns: string
- Panel ID
Example:
import MyPanelComponent from './MyPanel.jsx';
const panelId = context.registerPanel({
name: 'custom-panel',
title: 'My Custom Panel',
icon: 'box',
component: MyPanelComponent,
position: 'right'
});
addMenuItem()
Adds an item to the application menu.
context.addMenuItem(menuItem)
Parameters:
menuItem
(object)name
(string) - Menu item namelabel
(string) - Display labelaction
(function) - Click handlershortcut
(string) - Keyboard shortcut
Returns: string
- Menu item ID
Example:
context.addMenuItem({
name: 'export-pdf',
label: 'Export as PDF',
action: () => {
// Export logic
},
shortcut: 'CommandOrControl+Shift+P'
});
showNotification()
Displays a notification to the user.
context.showNotification(notification)
Parameters:
notification
(object)message
(string) - Notification messagetype
(string) - ‘info’ | ‘success’ | ‘warning’ | ‘error’duration
(number) - Display duration in ms (default: 3000)
Example:
context.showNotification({
message: 'Operation completed successfully!',
type: 'success',
duration: 3000
});
showDialog()
Shows a modal dialog.
await context.showDialog(dialog)
Parameters:
dialog
(object)title
(string) - Dialog titlemessage
(string) - Dialog messagebuttons
(array) - Button definitionstype
(string) - ‘confirm’ | ‘prompt’ | ‘alert’
Returns: Promise<any>
- User response
Example:
const result = await context.showDialog({
title: 'Confirm Action',
message: 'Are you sure you want to proceed?',
buttons: [
{ label: 'Cancel', value: false },
{ label: 'OK', value: true, primary: true }
],
type: 'confirm'
});
if (result) {
// User clicked OK
}
Filesystem API
readFile()
Reads a file from the workspace.
await context.readFile(filePath)
Parameters:
filePath
(string) - Absolute file path
Returns: Promise<string>
- File content
Requires Permission: read:files
Example:
try {
const content = await context.readFile('/path/to/file.md');
console.log(content);
} catch (error) {
console.error('Failed to read file:', error);
}
writeFile()
Writes content to a file.
await context.writeFile(filePath, content)
Parameters:
filePath
(string) - Absolute file pathcontent
(string) - Content to write
Returns: Promise<void>
Requires Permission: write:files
Example:
await context.writeFile('/path/to/file.md', '# New Content');
fileExists()
Checks if a file exists.
await context.fileExists(filePath)
Parameters:
filePath
(string) - Absolute file path
Returns: Promise<boolean>
Requires Permission: read:files
Example:
const exists = await context.fileExists('/path/to/file.md');
if (exists) {
// File exists
}
Settings API
getSetting()
Gets a plugin setting value.
await context.getSetting(key, defaultValue)
Parameters:
key
(string) - Setting keydefaultValue
(any) - Default value if not set
Returns: Promise<any>
- Setting value
Example:
const apiKey = await context.getSetting('apiKey', '');
const maxItems = await context.getSetting('maxItems', 10);
setSetting()
Sets a plugin setting value.
await context.setSetting(key, value)
Parameters:
key
(string) - Setting keyvalue
(any) - Setting value (JSON serializable)
Returns: Promise<void>
Example:
await context.setSetting('apiKey', 'secret-key-123');
await context.setSetting('theme', 'dark');
getAllSettings()
Gets all plugin settings.
await context.getAllSettings()
Returns: Promise<object>
- Settings object
Example:
const settings = await context.getAllSettings();
console.log(settings);
// { apiKey: 'secret-key-123', theme: 'dark', ... }
saveAllSettings()
Saves all plugin settings.
await context.saveAllSettings(settings)
Parameters:
settings
(object) - Settings object
Returns: Promise<void>
Example:
const settings = {
apiKey: 'new-key',
theme: 'light',
enabled: true
};
await context.saveAllSettings(settings);
Event System
on()
Registers an event listener.
context.on(event, handler)
Parameters:
event
(string) - Event namehandler
(function) - Event handler
Example:
context.on('file:save', (data) => {
console.log('File saved:', data.path);
});
context.on('editor:selection', (selection) => {
console.log('Selection changed:', selection);
});
Available Events:
file:save
- File savedfile:open
- File openedfile:close
- File closededitor:selection
- Selection changededitor:content
- Content changedworkspace:change
- Workspace changedplugin:enabled
- Plugin enabledplugin:disabled
- Plugin disabled
off()
Removes an event listener.
context.off(event, handler)
Parameters:
event
(string) - Event namehandler
(function) - Event handler
Example:
const handler = (data) => console.log(data);
context.on('file:save', handler);
// Later...
context.off('file:save', handler);
emit()
Emits a custom event.
context.emit(event, data)
Parameters:
event
(string) - Event namedata
(any) - Event data
Example:
context.emit('custom:event', { message: 'Hello' });
removeAllListeners()
Removes all event listeners.
context.removeAllListeners()
Example:
// In deactivate() method
deactivate() {
this.context.removeAllListeners();
}
Permissions System
Plugins must declare required permissions in plugin.json
:
Available Permissions
File Operations:
read:files
- Read workspace fileswrite:files
- Write to workspace filesread:workspace
- Read workspace metadatawrite:workspace
- Modify workspace metadata
Network:
network:http
- Make HTTP requestsnetwork:https
- Make HTTPS requests
UI:
ui:editor
- Modify editor UIui:sidebar
- Add sidebar panelsui:toolbar
- Add toolbar buttons
System:
execute:commands
- Execute system commandsstorage:local
- Access local storageclipboard:read
- Read clipboardclipboard:write
- Write to clipboard
hasPermission()
Checks if plugin has a permission.
context.hasPermission(permission)
Parameters:
permission
(string) - Permission name
Returns: boolean
Example:
if (context.hasPermission('read:files')) {
const content = await context.readFile('/path/to/file.md');
}
Utility Methods
isValidPath()
Validates a file path for security.
context.isValidPath(path)
Parameters:
path
(string) - File path
Returns: boolean
Example:
const isValid = context.isValidPath('/path/to/file.md');
if (isValid) {
// Safe to use path
}
Security Note: Prevents directory traversal attacks (../, etc.)
Plugin Lifecycle
Activation
- Plugin is loaded by PluginManager
activate(context)
is called with PluginAPI instance- Plugin registers extensions, commands, panels, etc.
- Event listeners are attached
Deactivation
deactivate()
is called- Plugin should clean up resources:
- Remove event listeners
- Unregister UI elements
- Cancel pending operations
- Save state if needed
deactivate() {
// Remove listeners
this.context.removeAllListeners();
// Clean up timers
if (this.timer) {
clearInterval(this.timer);
}
// Save state
this.context.setSetting('lastState', this.state);
}
Best Practices
Error Handling
activate(context) {
try {
// Plugin initialization
context.addExtension(myExtension);
} catch (error) {
console.error('Plugin activation failed:', error);
// Show user-friendly error
context.showNotification({
message: 'Plugin failed to load',
type: 'error'
});
}
}
Async Operations
activate(context) {
// Don't block activation
this.initialize(context);
}
async initialize(context) {
try {
const data = await context.getSetting('data');
// Process data
} catch (error) {
console.error('Initialization failed:', error);
}
}
Resource Cleanup
deactivate() {
// Always clean up
this.context.removeAllListeners();
// Cancel pending requests
if (this.abortController) {
this.abortController.abort();
}
// Save state
this.saveState();
}
Permission Checks
async readUserFile(path) {
if (!this.context.hasPermission('read:files')) {
throw new Error('No permission to read files');
}
return await this.context.readFile(path);
}
Example Plugins
Simple Toolbar Button
export default class HelloPlugin {
activate(context) {
context.addToolbarButton({
name: 'say-hello',
label: 'Say Hello',
icon: 'smile',
action: () => {
context.showNotification({
message: 'Hello from plugin!',
type: 'info'
});
}
});
}
deactivate() {}
}
Custom Slash Command
export default class DatePlugin {
activate(context) {
context.addSlashCommand({
name: 'today',
description: 'Insert today\'s date',
handler: (editor) => {
const date = new Date().toLocaleDateString();
editor.commands.insertContent(date);
}
});
}
deactivate() {}
}
File Watcher
export default class AutoSavePlugin {
activate(context) {
this.context = context;
this.isDirty = false;
context.on('editor:content', () => {
this.isDirty = true;
});
this.timer = setInterval(() => {
if (this.isDirty) {
this.autoSave();
}
}, 60000); // Every minute
}
async autoSave() {
try {
const content = this.context.getEditorContent();
// Save logic
this.isDirty = false;
this.context.showNotification({
message: 'Auto-saved',
type: 'success',
duration: 1000
});
} catch (error) {
console.error('Auto-save failed:', error);
}
}
deactivate() {
clearInterval(this.timer);
this.context.removeAllListeners();
}
}
Debugging
Console Logging
activate(context) {
console.log('Plugin activated:', this.constructor.name);
context.on('file:save', (data) => {
console.log('File saved event:', data);
});
}
Error Handling
try {
const result = await context.readFile(path);
} catch (error) {
console.error('Error details:', {
message: error.message,
stack: error.stack,
plugin: this.constructor.name
});
}
Security Considerations
- Path Validation - Always validate file paths
- Permission Checks - Check permissions before operations
- Input Sanitization - Sanitize user input
- XSS Prevention - Escape HTML in notifications/dialogs
- Rate Limiting - Limit API calls and operations
- Error Messages - Don’t expose sensitive information
Next Steps
- Tauri Commands Reference - Backend commands
- MCP Server API - MCP integration
- Configuration - Configure plugins
- Advanced Customization - Advanced plugin techniques