Create a New User Plugin
Help query: "How do I create a new user plugin?" / "How do I add a plugin?" / "How do I write a PluginPackage?"
There is no scaffold CLI in cargo xtask today. Creating a plugin is a short manual checklist: add a Rust module, export package(), register it in user/lib.rs, then build the user package.
If you already have an empty module file, skip ahead to After scaffold — what next?.
Walkthrough: a hello plugin
This plugin logs a greeting from the command palette and binds Ctrl+Shift+h.
Step 1 — Create the module file
Create user/hello.rs:
use editor_plugin_api::{
PluginAction, PluginCommand, PluginKeyBinding, PluginKeymapScope,
PluginPackage,
};
/// Returns the metadata for the hello package.
pub fn package() -> PluginPackage {
PluginPackage::new("hello", true, "A simple greeting plugin.")
.with_commands(vec![
PluginCommand::new(
"hello.greet",
"Logs a friendly greeting to the message log.",
vec![PluginAction::log_message("Hello from Volt!")],
),
])
.with_key_bindings(vec![
PluginKeyBinding::new(
"Ctrl+Shift+h",
"hello.greet",
PluginKeymapScope::Global,
),
])
}The package() function is the whole metadata surface the host needs for a minimal plugin.
Step 2 — Register the module
In user/lib.rs, declare the module with the other pub mod lines:
/// A simple greeting plugin.
pub mod hello;Add it to packages():
pub fn packages() -> Vec<PluginPackage> {
let mut pkgs = vec![
buffer::package(),
acp::package(),
// ... existing packages ...
hello::package(),
];
pkgs.extend(lang::packages());
pkgs
}If either step is missing, the plugin will not load — this is the most common "I created a file but nothing happened" failure.
Step 3 — Build and test
cargo build -p volt-user
cargo xtask test
cargo run -p volt -- --shell-hidden
cargo run -p volt -- --bootstrap-demoThen in the editor:
- Open the command palette (
F3or:) - Run
hello.greet - Or press
Ctrl+Shift+h
Patterns beyond hello
Hook-only commands
Many builtins only emit hooks and let the host implement behavior. See user/pane.rs:
PluginCommand::new(
"pane.split-horizontal",
"Splits the active workspace horizontally.",
vec![PluginAction::emit_hook("ui.pane.split-horizontal", None::<&str>)],
)Buffer + evaluator
For split-pane plugin buffers, copy the structure of user/calculator.rs:
- Declare a
PluginBufferwith sections - Emit
plugin_hooks::EVALUATEfrom a command - Implement
evaluate(input: &str) -> Vec<String> - Match your handler id in
UserLibraryImpl::run_plugin_buffer_evaluatorinuser/lib.rs
Current section API:
PluginBufferSections::new(vec![
PluginBufferSection::new("Input")
.with_writable(true)
.with_initial_lines(initial_buffer_lines()),
PluginBufferSection::new("Output")
.with_min_lines(1)
.with_initial_lines(vec!["…".to_owned()])
.with_update(PluginBufferSectionUpdate::Replace),
])Language packages
Language support belongs under user/lang/. Prefer the common helper documented in Add a language.
Autocomplete providers
Define autocomplete_provider() in your module and register it in user/autocomplete.rs backends(). See Autocomplete providers.
Runtime YAML
If your plugin needs user-tunable settings without a rebuild, add a child YAML file and extend user/config.rs. See Configuration.
Checklist
- [ ]
user/<name>.rsexportspub fn package() -> PluginPackage - [ ]
pub mod <name>;inuser/lib.rs - [ ]
<name>::package()listed inpackages() - [ ] Extra trait hooks wired (evaluator / autocomplete) if needed
- [ ]
cargo build -p volt-usersucceeds - [ ] Command appears in palette / bootstrap demo
- [ ]
cargo xtask clippyclean