The Registration API provides Lua scripts with the ability to dynamically register commands and event handlers without modifying plugin.yml.
These functions are available globally in Lua scripts without any prefix.
Register a command dynamically.
Syntax:
local success = registerCommand(name, permission, handler, [tabCompleter])Parameters:
name(string): Command name (must match[a-zA-Z0-9_-]+)permission(string): Permission node required to use the commandhandler(function): Function called when the command is executedsender(CommandSender): The command senderargs(table): Array of command arguments
tabCompleter(function, optional): Function called for tab completionsender(CommandSender): The command senderargs(table): Array of current arguments- Returns: Table of string suggestions
Returns:
success(boolean): true on success, nil on failure
Example:
-- Basic command
registerCommand("greet", "myplugin.greet", function(sender, args)
sender:sendMessage("Greetings!")
end)
-- Command with tab completion
registerCommand("teleport", "myplugin.teleport", function(sender, args)
sender:sendMessage("Teleporting to " .. args[1])
end, function(sender, args)
return {"world", "world_nether", "world_the_end"}
end)Register an event handler.
Syntax:
local success = registerEvent(eventClassName, [priority], handler)Parameters:
eventClassName(string): Fully qualified event class namepriority(string, optional): Event priority (default:"NORMAL")- Valid values:
"LOWEST","LOW","NORMAL","HIGH","HIGHEST","MONITOR"
- Valid values:
handler(function): Function called when the event firesevent(Event): The event object
Returns:
success(boolean): true on success, nil on failure
Example:
-- Default priority
registerEvent("org.bukkit.event.player.PlayerJoinEvent", function(event)
event:getPlayer():sendMessage("Welcome!")
end)
-- Custom priority
registerEvent("org.bukkit.event.block.BlockBreakEvent", "HIGHEST", function(event)
event:setCancelled(true)
event:getPlayer():sendMessage("You cannot break blocks here!")
end)registerCommandandregisterEventmust be called from the main thread. Registration mutates the server's command map and the event's handler list, neither of which is thread safe, so a call from an asynchronous scheduler callback is refused with a logged error and returnsnil. Wrap it inSchedulerApi:runSyncif you really need to register from one- Command names must match the pattern
[a-zA-Z0-9_-]+ - Command permissions are checked automatically by the server
- Event handlers are automatically unregistered when the script is reloaded
- Tab completers are optional but recommended for user-friendly commands
- Event priority determines the order in which handlers are called
- MONITOR priority handlers cannot cancel events
- Handlers always run on the main server thread. When an asynchronous event (for example
AsyncPlayerChatEvent) fires, the firing thread waits while the handler runs on the main thread, so cancelling and mutating the event still works. That wait times out after 10 seconds to avoid hanging the server during shutdown. - Every command execution, tab completion, and event handler call is capped by the
script-timeout-mssetting (5 seconds by default; see the Configuration section). Each invocation gets its own full budget. A handler that overruns is aborted and the abort is logged with the line it was stuck on. An aborted event handler does not stop the event from reaching the remaining listeners; an aborted command returnsfalse, so the sender sees the command's usage message rather than the "Error executing Lua command" chat message that an ordinary script error produces; an aborted tab completer returns no completions. - The abort cannot be caught with
pcall, so cleanup written in an error branch does not run when a handler is aborted. - Those two timeouts interact if you raise the budget: with
script-timeout-msabove 10000, an async event's firing thread gives up after its 10-second wait and continues while the handler is still running on the main thread, which means changes the handler makes to the event may come too late to take effect. Keepingscript-timeout-msbelow 10000 avoids this entirely.