Project structure
A well-organized extension looks like this:plugin.json file in the project. The @PluginManifest annotation generates it at compile time.
The entry class
Your extension’s entry point is a class that implementsKlyxPlugin and is annotated with @PluginManifest. The compiler plugin reads the annotation and generates a PluginDescriptor companion property plus a plugin.json file — the entryClass is derived from the class’s fully qualified name automatically.
register and toolbar.register do not take this as an argument. They use Kotlin context receivers — the KlyxPlugin is provided as a context parameter.
Registering resources
Screens
Register screens duringonLoad() using ScreenRegistry. Each screen gets a unique ScreenId and a composable lambda.
Toolbar actions
Add actions to Klyx’s toolbar. Each action has an ID, label, icon, category, priority, and click handler. Higher priority values place the action first within its category.ToolbarIcon supports four source types:
Predefined categories are available:
ToolbarCategory.CurrentFile, ToolbarCategory.Workspace, ToolbarCategory.Run, ToolbarCategory.Tools, and ToolbarCategory.Plugins (the default).
File openers
Register aFileOpener to handle custom file types. The opener returns a WorkspaceTab if it can handle the file, or null to let other openers try.
WorkspaceTab types
The
Custom tab accepts additional optional parameters: onClose (a suspend lambda called when the tab is closed) and pluginId (for ownership tracking).
File runners
Register aFileRunner to claim files for the editor’s Run button. The registry consults runners in descending priority order and delegates to the first one whose supports returns true.
runner.openScreen(...). See File Runners for the full API.
Installing tooling interactively
To install or configure tooling that prompts the user (e.g.rustup component add rust-analyzer), use TerminalManager from anywhere in your plugin — not just the Run button:
runInTerminal runs the command in a fresh session (no login shell or MOTD) with its stdin wired to the terminal. openTerminal opens an interactive login shell. See Terminal for details.
Responding to events
Use the event bus to react to app events. Subscribe inonLoad() and keep a reference to the subscription for cleanup.
Navigation
Use theNavigator service to navigate users to different destinations:
Language servers
Register LSP providers for custom file types. Multiple providers can be registered for the same extension, and they will all be queried in parallel:Language grammars
Register tree-sitter grammar providers to add syntax highlighting for custom languages:Process execution
Run shell commands and system programs:Best practices
- Keep
onLoad()fast: Defer heavy work toonStart()or launch a coroutine onpluginScope - Unregister everything: Always clean up screens, toolbar actions, file openers, LSP providers, and event subscriptions in
onUnload() - Handle file openers gracefully: Return
nullfromopen()if you cannot handle the file - Use the right coroutine scope: Use
pluginScopefor tasks that should persist across start/stop cycles, andcurrentLifecycleOwner().lifecycleScopefor tasks tied to the active state - Use
withResources: Wrap Compose content withwithResources { }so resource lookups resolve against the plugin’s own APK resources - Bundle icons as ImageVector: They scale well at any size
- Test with a real plugin: Build and install a plugin to verify it works end-to-end