Skip to content

Lua Plugin Author Guide

This guide covers everything you need to write a production-ready Lua plugin. You will learn how to structure a plugin, handle events, call host functions (world queries, KV storage, session data), and declare capability modules through your manifest’s requires list.

Prerequisites: a working HoloMUSH environment and a basic familiarity with Lua. If you have not built your first plugin yet, start with Getting Started with Plugins.

Lua plugins run inside a sandboxed gopher-lua VM. They are ideal for simple event reactions, formatting, and game commands that do not need persistent storage beyond the KV store.

For the full plugin system overview, see Plugin Guide.

plugins/my-plugin/
plugin.yaml # Manifest
main.lua # Entry point

Lua plugins implement a single on_event function:

function on_event(event)
if event.type ~= "say" then return nil end
if event.actor_kind == "plugin" then return nil end
local msg = event.payload:match('"message":"([^"]*)"')
if not msg then return nil end
return {
{
stream = event.stream,
type = "say",
payload = '{"message":"Echo: ' .. msg .. '"}'
}
}
end

All Lua plugins have access to host functions via the holomush global table and the holo standard library. Additional capability modules are injected based on the requires list in the manifest.

FunctionSignatureDescription
holomush.log(level, message)Log at debug/info/warn/error
holomush.new_request_id() -> stringGenerate a ULID
holomush.kv_get(key) -> value, errRead from KV store
holomush.kv_set(key, value) -> nil, errWrite to KV store
holomush.kv_delete(key) -> nil, errDelete from KV store
holomush.query_location(id) -> table, errGet location by ID
holomush.query_character(id) -> table, errGet character by ID
holomush.query_location_characters(id, [opts]) -> table, errList characters at a location
holomush.query_object(id) -> table, errGet object by ID
holomush.create_location(name, desc, type) -> table, errCreate a location
holomush.create_exit(from, to, name, [opts]) -> table, errCreate an exit between locations
holomush.create_object(name, opts) -> table, errCreate an object
holomush.find_location(name) -> table, errFind location by name
holomush.set_property(entity_type, id, property, value) -> true, errSet entity property
holomush.get_property(entity_type, id, property) -> value, errGet entity property
holomush.list_commands(character_id) -> table, errList available commands
holomush.get_command_help(name, character_id) -> table, errGet command help details
FunctionSignatureDescription
holo.fmt.bold(text) -> stringBold text
holo.fmt.italic(text) -> stringItalic text
holo.fmt.dim(text) -> stringDim text
holo.fmt.underline(text) -> stringUnderlined text
holo.fmt.color(color, text) -> stringColored text
holo.fmt.list(items) -> stringBulleted list from array table
holo.fmt.pairs(table) -> stringKey-value pair display
holo.fmt.table({headers, rows}) -> strFormatted table
holo.fmt.separator() -> stringVisual separator line
holo.fmt.header(text) -> stringSection header
holo.fmt.parse(text) -> stringParse inline markup
FunctionSignatureDescription
holo.emit.location(location_id, event_type, payload)Queue event to a location
holo.emit.character(character_id, event_type, payload)Queue event to a character
holo.emit.global(event_type, payload)Queue a global event
holo.emit.flush() -> table or nilFlush and return queued events

Available when session.Access is configured on the host (always in production):

FunctionSignatureDescription
holo.session.find_by_name(name) -> table or nil, errFind session by character name
holo.session.set_last_whispered(session_id, name)Record last whisper target

The returned session table contains character_id, character_name, and location_id.

These modules are injected only when the corresponding proto service is listed in the manifest’s requires field. Each module registers a global Lua table.

FunctionSignatureDescription
session.find_by_name(name) -> table or nil, errFind session by character name
session.list_active() -> table, errList all active sessions
session.broadcast(message) -> nil, errBroadcast system message
session.set_last_whispered(session_id, name)Record last whisper target
session.disconnect(session_id, reason) -> nil, errForcibly disconnect a session

Session table fields: id, character_id, character_name, location_id, grid_present, last_whispered.

alias.* (provided to the bundled core-aliases plugin; not declarable via requires)

Section titled “alias.* (provided to the bundled core-aliases plugin; not declarable via requires)”
FunctionSignatureDescription
alias.set_player(player_id, name, command)Create/update player alias
alias.delete_player(player_id, name)Remove player alias
alias.list_player(player_id) -> tableList player’s aliases
alias.check_shadow(name) -> tableCheck if alias shadows command
alias.set_system(name, command, created_by)Create/update system alias
alias.delete_system(name)Remove system alias
alias.list_system() -> tableList system aliases

Alias entry tables contain alias and command fields. check_shadow returns {shadows = bool, command = string}.

property.* (requires capability: property)

Section titled “property.* (requires capability: property)”
FunctionSignatureDescription
property.list_by_parent(subject_id, parent_type, parent_id) -> tableList properties by parent
property.find_by_prefix(prefix) -> tableFind properties by name prefix
property.update_character_description(subject_id, character_id, description)Set character description

Property tables contain name, value, visibility. Extended results from find_by_prefix also include parent_type and parent_id.

world_ext.* (requires capability: world.query)

Section titled “world_ext.* (requires capability: world.query)”

Extended world queries beyond the always-available holomush.query_* functions:

FunctionSignatureDescription
world_ext.get_objects_by_location(subject_id, location_id) -> tableAll objects at a location
world_ext.get_characters_by_location(subject_id, location_id) -> tableAll characters at a location

Object tables: id, name, description, location_id, owner_id. Character tables: id, player_id, name, description, location_id.

Host functions return errors as a second return value (nil, err). Check errors before using results:

local location, err = holomush.query_location(location_id)
if err then
holomush.log("warn", "query failed: " .. err)
return nil
end
-- Use location.name, location.description, etc.

Common error messages: "not found", "access denied", "query timed out", "internal error (ref: XXXX...)". See Plugin Guide for details on the correlation ID pattern.

name: my-social-plugin
version: 1.0.0
type: lua
requires:
- capability: session
- capability: property
events:
- say
- arrive
policies:
- name: "emit-events"
dsl: |
permit(principal is plugin, action in ["emit"], resource is stream) when {
principal.plugin.name == "my-social-plugin"
};
lua-plugin:
entry: main.lua

With these requires, your main.lua gets access to both session.* and property.* global tables in addition to the always-available holomush.* and holo.* functions.