Skip to content

PLUGINS SDK

github-actions[bot] edited this page Jul 28, 2026 · 4 revisions

Plugin SDK

This page is the compact capability reference. The public contracts live below sdk/; host implementations follow the same capability first navigation as realms, under internal/plugin/{command,event,player,route,permission,...}. Use PLUGINS-CREATING, PLUGINS-LISTENERS, and PLUGINS-COMMANDS for the complete tutorials.

Registration and permissions

Register(plugin.Host) error is called once at startup. Register all capabilities there and return the first error: a partial or failed plugin is disabled. Permission names passed to Permissions().Register are local, while checks and command requirements use their full name:

host.Permissions().Register("hello.use", "Use the hello command")
// Full node: plugin.example-plugin.hello.use

The node joins Pixels' normal permission catalog and can be granted through the existing group or direct-player administration API. Plugins do not maintain a parallel rank system.

Players and packet interceptors

host.Players().All() and Find(id) return copied sdk/player.Player values. Message, Disconnect and HasPermission are bounded player actions. Message renders as a Nitro system alert.

Intercept registers global middleware when Header is nil, or middleware for one inbound packet header otherwise. Larger priorities execute first; equal priorities preserve registration order. Calling next(ctx) advances to the next plugin and eventually the native handler. Returning without calling it cancels the packet. Payload bytes are copied before entering plugin code.

host.Players().Intercept(func(ctx context.Context, packet plugin.InterceptContext, next plugin.Next) error {
	log.Printf("inbound header=%d player=%d", packet.Header, packet.Player.ID)
	return next(ctx)
}, plugin.InterceptOptions{Priority: plugin.PriorityLow})

Economy, rooms, and trades

SDK 2.x adds capability-scoped actions instead of exposing realm services:

  • Economy().Grant, Set, Balance, and Types use the configured currency catalog. Plugin mutations are audited as actor plugin with a scoped reason.
  • Rooms().Find, Update, Occupants, and SetMuteAll return or accept SDK snapshots only. Updates reuse native validation and optimistic locking.
  • Trades().Active returns copied participant/item state. ForceCancel closes only an existing live trade with the plugin scope in the audit reason.
balance, err := host.Economy().Grant(playerID, -1, 25)
room, found := host.Rooms().Find(roomID)
trade, active := host.Trades().Active(playerID)

The host never exposes repositories, database transactions, active-room objects, or mutable player records.

Events

The plugin event hub is separate from Pixels' post-commit internal bus. A plugin can subscribe but cannot publish arbitrary realm events. SDK 2.x bridges every committed realm fact as immutable event.Published, provides typed lifecycle notifications such as player.connected, inventory.currency_changed, and command.attempted, and exposes the bounded pre-commit events listed in PLUGINS-LISTENERS.

Listeners run from larger to smaller priority. IgnoreCancelled skips a listener when a previous one already vetoed a cancellable event. A failure in one listener is logged and does not stop healthy listeners. Every mutable event is cloned per callback and revalidated by its owning realm before persistence.

Chat commands

Commands use go.minekube.com/brigodier. Messages beginning with PIXELS_COMMAND_PREFIX are consumed by the command tree before normal room chat. They never appear as literal speech, including unknown, denied and malformed commands.

root := brigodier.Literal("hello").
	Requires(command.RequiresPermission("plugin.example-plugin.hello.use")).
	Then(brigodier.Argument("name", brigodier.StringPhrase).
		Executes(brigodier.CommandFunc(func(call *brigodier.CommandContext) error {
			sender, _ := command.SenderFrom(call.Context)
			return sender.Reply(call.Context, "Hello, "+call.String("name"))
		})))

err := host.Commands().Register(root)

Only one plugin can own a root literal. Command feedback is localized by the host; Sender deliberately supports players and future console/system callers.

HTTP routes and OpenAPI

Routes are mounted only below /plugins/<manifest-name>, after the same global X-API-Key middleware as every private Pixels route. A plugin cannot claim another namespace. Describe accepts a valid JSON document and serves it at /plugins/<name>/openapi.json; plugin paths never mutate Pixels' central OpenAPI document.

host.Routes().Mount("example-plugin", func(router fiber.Router) {
	router.Get("/health", func(ctx *fiber.Ctx) error {
		return ctx.JSON(fiber.Map{"status": "ok"})
	})
})

Clone this wiki locally