Skip to main content
This guide covers the full process of creating a Klyx extension, from project setup through the plugin manifest annotation, to advanced features like screens, toolbar actions, file openers, events, and language registries.

Project structure

A well-organized extension looks like this:
There is no 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 implements KlyxPlugin 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.
Notice how 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 during onLoad() 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 a FileOpener 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 a FileRunner 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.
Runners that preview a file instead of running a command can navigate to a plugin screen with 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 in onLoad() and keep a reference to the subscription for cleanup.
The event bus also supports subscribing within a coroutine scope (auto-cancels when the scope ends):
See Events for all available event types and the full event bus API. Use the Navigator 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:
See Language Server Protocol for details.

Language grammars

Register tree-sitter grammar providers to add syntax highlighting for custom languages:
See Language Grammars for the full grammar registration API.

Process execution

Run shell commands and system programs:
See Process Execution for the full command and pipeline API.

Best practices

  • Keep onLoad() fast: Defer heavy work to onStart() or launch a coroutine on pluginScope
  • Unregister everything: Always clean up screens, toolbar actions, file openers, LSP providers, and event subscriptions in onUnload()
  • Handle file openers gracefully: Return null from open() if you cannot handle the file
  • Use the right coroutine scope: Use pluginScope for tasks that should persist across start/stop cycles, and currentLifecycleOwner().lifecycleScope for tasks tied to the active state
  • Use withResources: Wrap Compose content with withResources { } 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