Plugin authoring
June 4, 2026 · View on GitHub
A complete walkthrough that turns a "hello world" plugin into
something useful. The full plugin system reference is
hollow.plugins and the guide is
Plugins. A working example lives at
examples/plugins/hollow-spirit.
The smallest possible plugin
A plugin is just a directory.
Create it anywhere stable — ~/code/hollow-hello is fine.
hollow-hello/
hollow_plugin/
hello.lua
-- hollow-hello/hollow_plugin/hello.lua
local hollow = require("hollow")
hollow.keymap.set("<leader>hi", function()
hollow.ui.notify.info("hello from a plugin", { ttl = 1500 })
end, { desc = "say hi" })
Register it from your personal config:
hollow.plugins.setup({
plugins = { "~/code/hollow-hello" },
})
On the next startup (or after <leader>uu), <leader>hi shows a
toast.
Adding a module with setup
Add a lua/ directory and a module file:
hollow-hello/
hollow_plugin/
hello.lua
lua/
hollow-hello/
init.lua
-- hollow-hello/lua/hollow-hello/init.lua
local M = {}
function M.setup(opts)
hollow.log("hollow-hello loaded with", opts and opts.message or "<no message>")
M._opts = opts or {}
end
function M.greet()
return M._opts.message or "hello from a plugin"
end
return M
setup is called once at startup with the opts table from the
plugin declaration. The module is then reachable via
require("hollow-hello") from any other Lua code in the same
Hollow process.
Pass opts from the loader:
hollow.plugins.setup({
plugins = {
{ "~/code/hollow-hello", opts = { message = "hi, hollow" } },
},
})
Using the module from autoload
-- hollow-hello/hollow_plugin/hello.lua
local hollow = require("hollow")
local hello = require("hollow-hello")
hollow.keymap.set("<leader>hi", function()
hollow.ui.notify.info(hello.greet(), { ttl = 1500 })
end, { desc = "say hi" })
Reacting to events
hollow-hello could react to terminal events:
-- hollow-hello/hollow_plugin/events.lua
local hollow = require("hollow")
hollow.events.on("term:bell", function(e)
hollow.ui.notify.warn("bell in " .. (e.pane.title or "<pane>"),
{ ttl = 1500 })
end)
hollow.events.on("workspace:new", function(e)
hollow.log("new workspace:", e.workspace.name)
end)
The shipped hollow-spirit example uses this pattern: an
autoloaded event listener, a module with setup(opts), and a small
toast notifier.
Distributing via git
Put the directory in a git repo and reference it with the
user/repo shorthand or a full URL:
hollow.plugins.setup({
plugins = {
"your-user/hollow-hello",
"https://gitlab.com/your-user/hollow-hello",
},
})
Hollow clones the repo into
hollow.fs.data_dir() .. "/plugins/hollow-hello" on first run.
Update with:
hollow.plugins.sync()
sync runs git pull --ff-only --recurse-submodules for each git
plugin; restart Hollow to pick up new code.
Layout reference
my-plugin/
lua/ -- prepended to package.path
my-plugin/
init.lua -- M.setup(opts) lives here
hollow_plugin/ -- autoloaded
*.lua -- or: hollow_plugin/my-plugin/init.lua
A plugin can be autoload-only with no lua/ directory.
A plugin can be module-only with no hollow_plugin/ directory.
The two halves are independent.
Error handling
The loader is forgiving.
A failed clone, a missing module, a throwing setup, and a broken
hollow_plugin/*.lua file all log a warning and let startup
continue. The runtime never aborts because of a plugin.
See also
- Plugins — guide
hollow.plugins— full APIhollow-spirit— working examplehollow.fs—data_dirand friends- Editor support (LuaLS) —
.luarc.jsonsetup for type hints