Getting Started
This guide will walk you through creating your first Lokus plugin from scratch. By the end, you’ll have a working plugin that adds custom commands and interacts with the editor.
Prerequisites
Before you begin, ensure you have:
- Node.js 18+ installed
- npm package manager
- Lokus installed on your system
- Basic knowledge of TypeScript
- A code editor (VS Code recommended)
Quick Start
The fastest way to create a new plugin is using the Lokus Plugin CLI:
# Install the CLI globally (one-time setup)
npm install -g @lokus/plugin-cli
# Create a new plugin with interactive prompts
lokus-plugin create my-first-plugin
# Or use quick mode with defaults
lokus-plugin create my-first-plugin --skip-prompts --template basic-typescript
# Navigate to the plugin directory
cd my-first-plugin
# Install dependencies
npm install
# Build the plugin
npm run build
# Link for development (creates symlink in ~/.lokus/plugins)
lokus-plugin link
# Validate the plugin
lokus-plugin validateNote: The CLI supports both
@lokus/plugin-cliandlokus-plugin-clipackage names for compatibility.
Available Templates
When creating a plugin, you can choose from these templates:
| Template | Description |
|---|---|
basic-typescript | Simple TypeScript plugin with commands |
basic-plugin | Minimal plugin structure |
react-ui-panel | Plugin with React-based UI panel |
ui-extension-plugin | Plugin with toolbar and status bar items |
language-support-plugin | Plugin for language/syntax support |
Use with: npx lokus-plugin create my-plugin --template react-ui-panel
Project Structure
Here’s what gets generated:
my-first-plugin/
├── src/
│ └── index.ts # Plugin entry point
├── dist/ # Compiled output (after build)
│ └── index.js
├── test/
│ ├── index.test.ts # Unit tests
│ └── setup.ts # Test setup
├── package.json # npm configuration
├── plugin.json # Plugin manifest
├── tsconfig.json # TypeScript configuration
├── esbuild.config.js # Build configuration
└── README.mdUnderstanding the Manifest
The plugin.json file defines your plugin’s metadata and capabilities:
{
"manifest": "2.0",
"id": "my-first-plugin",
"name": "my-first-plugin",
"displayName": "My First Plugin",
"version": "0.1.0",
"description": "A Lokus plugin",
"author": "Your Name",
"license": "MIT",
"main": "./dist/index.js",
"lokusVersion": ">=1.0.0",
"permissions": [
"editor:read",
"editor:write",
"commands:register",
"ui:notifications"
],
"activationEvents": ["onStartup"]
}Key Manifest Fields
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier (lowercase, hyphens) |
name | Yes | Plugin name |
version | Yes | Semantic version (e.g., “1.0.0”) |
main | Yes | Path to compiled entry point |
permissions | No | Required API permissions |
activationEvents | No | When to activate the plugin |
Creating Your First Plugin
Step 1: Define the Plugin Class
Edit src/index.ts:
import { PluginContext } from 'lokus-plugin-sdk';
export default class MyFirstPlugin {
private context: PluginContext;
private logger: PluginContext['logger'];
constructor(context: PluginContext) {
this.context = context;
this.logger = context.logger;
this.logger.info('MyFirstPlugin initialized');
}
async activate(activationContext: any): Promise<void> {
this.logger.info('Activating MyFirstPlugin...');
try {
// Register a command
activationContext.commands.registerCommand(
'myFirstPlugin.sayHello',
{
name: 'Say Hello',
description: 'Shows a hello notification',
callback: () => {
this.context.ui.showInformationMessage('Hello from MyFirstPlugin!');
this.logger.info('Hello command executed');
}
}
);
this.logger.info('MyFirstPlugin activated successfully');
} catch (error) {
this.logger.error('Failed to activate plugin:', error);
throw error;
}
}
async deactivate(): Promise<void> {
this.logger.info('Deactivating MyFirstPlugin...');
// Cleanup resources here
this.logger.info('MyFirstPlugin deactivated');
}
}Step 2: Understanding the Plugin Context
When your plugin is instantiated, it receives a PluginContext with access to all 14 Lokus APIs:
interface PluginContext {
// Plugin metadata
pluginId: string;
pluginPath: string;
// Logging
logger: {
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
};
// Core APIs (always available)
editor: EditorAPI; // 95+ methods for TipTap editor
ui: UIAPI; // Panels, dialogs, notifications
commands: CommandsAPI; // Command registration
workspace: WorkspaceAPI; // File and editor access
config: ConfigurationAPI; // Plugin settings
// Language & Features
languages: LanguagesAPI; // Completion, hover, etc.
// Terminal & Tasks
terminal: TerminalAPI; // Terminal management
tasks: TaskAPI; // Task execution
// Debugging & Themes
debug: DebugAPI; // Debug sessions
themes: ThemeAPI; // Theme management
// Storage & Files
storage: StorageAPI; // Key-value storage
fs: FilesystemAPI; // File operations
// Network & System
network: NetworkAPI; // HTTP requests (requires permission)
clipboard: ClipboardAPI; // Clipboard access
}See API Reference for complete documentation of all APIs.
Step 3: Using the Activation Context
The activate method receives an activation context with command registration:
async activate(activationContext: any): Promise<void> {
// Register commands
activationContext.commands.registerCommand(
'commandId',
{
name: 'Display Name',
description: 'What this command does',
callback: () => {
// Command implementation
}
}
);
}Step 4: Build and Test
# Build the plugin
npm run build
# Run tests
npm test
# Validate manifest
npx lokus-plugin validateDevelopment Workflow
1. Link Your Plugin
# Creates symlink in ~/.lokus/plugins/
npx lokus-plugin link2. Rebuild on Changes
# Watch mode (if configured)
npm run dev
# Or manual rebuild
npm run build3. Restart Lokus
After rebuilding, restart Lokus to load the updated plugin.
4. Check Logs
Open Lokus Developer Tools (View → Developer → Toggle Developer Tools) to see plugin logs.
Working with the Editor API
The EditorAPI provides 95+ methods for deep TipTap editor integration:
async activate(activationContext: any): Promise<void> {
const editor = this.context.editor;
// Add a slash command
await editor.addSlashCommand({
name: 'my-command',
description: 'Insert custom content',
icon: '🎉',
execute: async () => {
await editor.insertNode('paragraph', {}, 'Hello World!');
}
});
// Add a toolbar button
editor.addToolbarItem({
id: 'my-toolbar-button',
title: 'My Button',
icon: 'star',
handler: () => {
this.context.ui.showInformationMessage('Toolbar button clicked!');
}
});
// Register custom TipTap extension
await editor.registerExtension({
name: 'customNode',
type: 'node',
schema: {
attrs: { data: { default: null } },
content: 'inline*',
group: 'block',
parseDOM: [{ tag: 'div.custom' }],
toDOM: (node) => ['div', { class: 'custom' }, 0]
}
});
// Get current selection
const selection = await editor.getSelection();
if (selection) {
this.context.logger.info('Selection:', selection);
}
}Key Editor Methods
The EditorAPI includes comprehensive methods for:
- Extensions:
registerExtension(),registerNode(),registerMark() - Slash Commands:
addSlashCommand(),removeSlashCommand() - UI Elements:
addToolbarItem(),addContextMenuItem(),addKeyboardShortcut() - Content Manipulation:
insertNode(),replaceSelection(),getText(),getSelection() - Node Views:
registerNodeView()for custom rendering - Input Rules:
registerInputRule()for automatic transformations - Format Detection:
registerFormat()for language support
See EditorAPI Reference for complete documentation of all 95+ methods.
Writing Tests
Create test/index.test.ts:
import { createMockPluginContext, createMockActivationContext } from 'lokus-plugin-sdk/testing';
import MyFirstPlugin from '../src/index';
describe('MyFirstPlugin', () => {
let plugin: MyFirstPlugin;
let mockContext: any;
let mockActivationContext: any;
beforeEach(() => {
mockContext = createMockPluginContext();
mockActivationContext = createMockActivationContext();
plugin = new MyFirstPlugin(mockContext);
});
afterEach(async () => {
await plugin.deactivate();
});
it('should initialize', () => {
expect(plugin).toBeDefined();
expect(mockContext.logger.info).toHaveBeenCalledWith('MyFirstPlugin initialized');
});
it('should activate and register commands', async () => {
await plugin.activate(mockActivationContext);
expect(mockActivationContext.commands.registerCommand).toHaveBeenCalledWith(
'myFirstPlugin.sayHello',
expect.any(Object)
);
});
it('should execute hello command', async () => {
await plugin.activate(mockActivationContext);
// Get the callback that was registered
const [, options] = mockActivationContext.commands.registerCommand.mock.calls[0];
options.callback();
expect(mockContext.ui.showInformationMessage).toHaveBeenCalledWith(
'Hello from MyFirstPlugin!'
);
});
});Run tests:
npm testPackaging and Publishing
Package Your Plugin
# Validate first
npx lokus-plugin validate
# Build
npm run build
# Package (creates .zip file)
npx lokus-plugin packagePublish to Registry
# Login (first time only)
npx lokus-plugin login
# Publish
npx lokus-plugin publishSee Publishing Guide for more details.
Troubleshooting
Plugin Won’t Load
-
Check the manifest:
npx lokus-plugin validate -
Verify the build output exists:
ls dist/index.js -
Check symlink:
ls -la ~/.lokus/plugins/
Commands Not Appearing
- Ensure command is registered in
activate()method - Check browser console for errors
- Verify
permissionsincludescommands:register
Build Errors
-
TypeScript errors:
npx tsc --noEmit -
Missing dependencies:
npm install
Explore More APIs
Now that you have a basic plugin, explore the other powerful APIs:
Terminal & Task Integration
// Create a terminal
const terminal = this.context.terminal.createTerminal({
name: 'Build',
cwd: this.context.workspace?.rootPath
});
terminal.sendText('npm run build');See TerminalAPI and TaskAPI.
UI Components
// Create a status bar item
const statusBar = this.context.ui.createStatusBarItem('left', 100);
statusBar.text = 'My Plugin Active';
statusBar.show();
// Show progress indicator
await this.context.ui.withProgress(
{ title: 'Processing...' },
async (progress) => {
progress.report({ message: 'Step 1' });
await doWork();
}
);See UIAPI for dialogs, panels, tree views, and more.
Language Features
// Add completion provider
this.context.languages.registerCompletionProvider('markdown', {
async provideCompletionItems(document, position) {
return [
{ label: 'todo', insertText: '- [ ] ', detail: 'Todo item' }
];
}
});See LanguagesAPI for hover, formatting, and code actions.
Next Steps
Core Documentation
- API Reference - Browse all 14 APIs with examples
- Plugin Manifest - Comprehensive manifest reference
- Plugin Lifecycle - Activation, deactivation, and resource management
Learn by Example
- Tutorials & Guides - Step-by-step tutorials
- Examples - Real-world plugin examples
- UI Components - React components for plugin UIs
Publishing
- Publishing Guide - Share your plugin on the marketplace
- CLI Tools - Command-line development tools