API v1 Native-event preview · September 2026
Native events review
GoCraft/Plugin API

Go + Java extensions · ABI v1

Write once.
Run in the shared world.

The GoCraft plugin API is designed around one edition-neutral host. Events, commands and permissions are resolved in GoCraft's core, so plugin behavior does not split into separate Java and Bedrock implementations.

LanguagesGo + JavaOut-of-process runtimes
ContractABI v1Shared generated schema
ClientsBothJava + Bedrock
01

Availability

Implemented runtimes, evolving API

Checked on 10 September 2026: Go/JVM runtimes and custom events are merged into main. The 14-event native API described below is on feat/go-events-api, with missing-items gameplay work included, and is still under review. Do not mix its examples with older released SDKs. GoCraft remains experimental.

AreaStatusWhat exists
Bundle hostImplementedStrict manifests, safe .gcpkg scanning and deterministic load order.
Event busImplementedShared budgets, priorities, failure policy, health and queued effects.
CommandsImplementedGo builders, Java annotations, neutral trees, permissions and runtime callbacks.
Go + JVM SDKsImplementedGo child processes, a shared JVM runtime, lifecycle hooks and paired examples.
Custom eventsIn mainGenerated provider/subscriber types, cancellation, mutable records and layout locks.
Native eventsIn review14 typed events; validated cancellation and mutation round trips for Go and Java.
Author toolingAvailablegocraft-cli v0.2.1 and the Java Gradle plugin build .gcpkg bundles.
Config + servicesSeparate workTyped configuration and cross-plugin services are not part of this native-event preview.
02

Two runtimes, one contract

Each Go plugin is a separate executable, not a Go -buildmode=plugin library. Java plugins use a separate Java 25 runtime that hosts multiple plugins. The Minecraft server and shared game state remain in Go.

Game tickGoCraft coreowns state
Language-neutralABI v1events + commands
Plugin runtimeGo or Javatyped callbacks
Return pathVerdict + effectsvalidated by the host
  • Structure is data. Event layouts and command trees cross the ABI; Go function values do not.
  • The host owns state. Allowed event mutations return in the verdict; additional host effects are queued for a safe tick.
  • One API serves both editions. Protocol-specific encoding stays in the Java and Bedrock adapters.
03

Getting started today

Use Go 1.26.0, JDK 25 and the checked-in Gradle wrapper. CLI v0.2.1 supports custom-event generation and packaging. For this native-event preview, use matching feat/go-events-api branches in GoCraft, the JVM repository and the examples; the Go example already pins the matching published SDK and ABI commits.

Terminal
git clone --branch feat/go-events-api https://github.com/GoCraft-MC/gocraft-jvm.git
git clone --branch feat/go-events-api https://github.com/GoCraft-MC/gocraft-plugin-examples.git
go install github.com/GoCraft-MC/gocraft-cli@v0.2.1
# Ensure your Go binary directory is on PATH.
(cd gocraft-jvm && ./gradlew publishToMavenLocal)
cd gocraft-plugin-examples
gocraft-cli gen -lang java -package gocraft.example.greeting \
  -o java/src/main/java/gocraft/example/greeting go
(cd java && sh ./gradlew gocraftBundle)
(cd go && SHOP_BUNDLE=../java/build/gocraft/gocraft-example-java.gcpkg ./build.sh)
Keep feature dependencies together.

These are POSIX-shell commands; use Git Bash on Windows and replace wrapper invocations with ./gradlew.bat. The Java example uses locally published feature artifacts named 0.3.0, not the released v0.3.0 API. Copy both bundles into a matching server's plugins/ folder and restart. See the complete example instructions for outputs and local CLI overrides.

04

Bundle layout

Every language ships the same .gcpkg ZIP format. The host reads and validates metadata before it starts a runtime.

example.gcpkg
plugin.toml          # validated manifest
commands.pb          # optional neutral command tree
bin/example-go      # Go executable at the manifest entry
payload/*.jar       # Java bundles instead carry plugin jars
assets/
  ...                # plugin-owned resources

The packer reads a staged directory; it does not compile source. Java's Gradle task stages jars automatically. The Go example script generates types, builds its binary with GOWORK=off, and checks events.lock.json before packing. Native executables must match the server's OS and architecture.

05

plugin.toml

The manifest lets GoCraft discover subscriptions and commands without executing plugin code. Unknown keys fail strict decoding.

plugin.toml
id = "dev.example.protect"
version = "0.1.0"
api = 1
runtime = "go"
entry = "bin/protect"

[[subscribe]]
event = "block.break"
priority = "high"
perms = ["protect.bypass"]

[[subscribe]]
event = "player.chat"

[commands]
tree = "commands.pb"
idLowercase identifier using letters, numbers, dots, dashes or underscores.Required
versionPlugin version displayed by the host.Required
apiABI major. The current and only accepted value is 1.Required
runtimego for a native executable; jvm for a Java plugin.Required
entryGo binary path or Java plugin class name.Runtime entry
subscribe.eventOne event per [[subscribe]] block, optionally with a priority.Per event
subscribe.permsPermission answers requested by this subscription only.Optional
commands.treePath to the generated tree. Omit this block for a plugin with no commands.Optional
06

Events

Native events are generated from the shared ABI schema into typed Go structs and Java classes. Only cancellable handlers receive EventControl. Register listeners and declare each subscription in the manifest.

Blocking

Cancellable

Go uses control.Cancel(); Java uses control.cancel(). The verdict prevents the original action.

Async

Observational

Scheduled away from the simulation tick with isolated payload copies and health tracking.

Safety

Failure policy

ALLOW fails open; DENY fails closed when a verdict is missing.

Go · inside your plugin's OnLoad
// import gocraft "github.com/GoCraft-MC/gocraft-api-go"
return ctx.Events().OnPlayerChat(func(event *gocraft.PlayerChatEvent, control gocraft.EventControl) {
    if event.Message == "hide-go" {
        control.Cancel()
        return
    }
    if event.Message == "hello-go" {
        event.Message = "Hello from the typed Go event API."
    }
})
Java · registered listener class
import fr.gocraft.api.EventControl;
import fr.gocraft.api.Subscribe;
import fr.gocraft.api.event.PlayerChatEvent;

public final class ChatListener {
    @Subscribe
    public void onChat(PlayerChatEvent event, EventControl control) {
        if ("hide-java".equals(event.message())) {
            control.cancel();
            return;
        }
        if ("hello-java".equals(event.message())) {
            event.setMessage("Hello from the typed Java event API.");
        }
    }
}

Go callbacks in one plugin share the event pointer and control. Accepted mutations reach the next plugin; cancellation stops later plugins. Subscriptions use priority order, then plugin ID. Observational notifications are asynchronous and cannot be cancelled.

Only declared mutable fields return through IPC. Changing a Go snapshot such as a player's name does not change server state; Java exposes setters only for mutable values. Chat rewritten to start with / remains chat, while command rewrites are reparsed and permission-checked.

Source: Native event semantics and hook timing. Complete registration and lifecycle code: Go and Java examples.

07

Native gameplay events

The preview has 14 native event types. The final column indicates whether cancellation is supported; mutable values are listed in the middle. All other values are snapshots.

player.joinAfter the player is reachable; no mutable fields.Notify
player.quitAfter removal from online players; no mutable fields.Notify
player.chatBefore broadcast; mutable message.Cancel
player.commandBefore parsing and permission checks; mutable command.Cancel
block.breakBefore block removal, drops and tool wear; no mutable fields.Cancel
block.placeAfter validation, before block writes and item consumption; no mutable fields.Cancel
player.damageAfter shield/armour checks, before resistance/absorption; mutable damage.Cancel
entity.damageNon-player damage before application or queue coalescing; mutable damage.Cancel
player.deathFatal health transition after totem resolution; no mutable fields.Notify
player.respawnAfter authoritative revival and position setup; no mutable fields.Notify
player.teleportCommand teleports in the current dimension; mutable X, Y and Z.Cancel
player.interactMain-hand block/entity use, not attack or dismount; no mutable fields.Cancel
inventory.clickJava container clicks or Bedrock inventory transactions; no mutable fields.Cancel
item.useMain-hand use in air before starting/consuming the item; no mutable fields.Cancel

Cancelled placement and inventory actions restore the client's prediction to canonical state. Bedrock multi-slot transactions use slot=-1, not Java's individual click gesture. Zero damage prevents application; non-finite numeric mutations are rejected.

Cancel with a queued message
// Inside OnLoad, with block.break declared in plugin.toml.
return ctx.Events().OnBlockBreak(func(event *gocraft.BlockBreakEvent, control gocraft.EventControl) {
    if !event.Can("protect.bypass") {
        control.Cancel()
        _ = event.Player.SendMessage("Protected area.")
    }
})
08

Commands

Commands are a neutral tree. Java renders it as Brigadier data, Bedrock renders it as Available Commands, and the plugin runtime only receives the selected local executor ID and typed values.

Go · declare shape and handler together
func (p *Plugin) Commands() *gocraft.CommandSet {
    set := gocraft.NewCommandSet()
    set.Command("greet").Permission("example.greet").Runs(func(call *gocraft.CommandContext) error {
        call.Reply("Hello, " + call.SenderName + "!")
        return nil
    })
    return set
}
The packer owns executor IDs.

Go's command dump and Java's annotation processor produce neutral metadata; gocraft-cli creates commands.pb. Handlers bind to command paths, not hard-coded IDs. The host namespaces collisions, parses arguments and checks permissions.

Argument types

integerdecimalstringgreedyplayerblock_posblock_stateitemdurationenumcustom

Core command names cannot be replaced. Plugin-versus-plugin collisions are namespaced deterministically, such as /economy:shop.

09

Permissions

Declare permission reads in the manifest. The host resolves them once against its group and operator model, then injects the answers into an event. Command branches carry their permission node and are pruned before a client can tab-complete them.

  • Event permissions come from subscribe.perms.
  • Command permissions use Go's .Permission(...) or Java's @Permission and command builders.
  • Invocation rechecks access on the server; client pruning is not treated as security.
  • Installing a plugin is trusting it. The manifest is not a process sandbox.
10

Effects and world writes

There are two return paths. Cancellation and allowed event-field mutations are validated before the guarded action continues. Additional effects, such as sending a message, go through the host's FIFO queue instead of writing game state from a plugin thread.

plugin effectHostCall queuenext tick drainsafe application

Calls enqueued while a drain is already running stay queued for the following tick. Closing the queue returns ErrMutationQueueClosed instead of silently accepting data.

11

Lifecycle

  1. 1
    Scan

    Read and validate all manifests and command trees without executing code.

  2. 2
    Preflight

    Provision only the runtime languages required by installed bundles.

  3. 3
    Register structure

    Resolve command conflicts before a runtime process starts.

  4. 4
    Load, warm and ready

    Start runtimes, attach subscriptions, perform handler-free JVM warm-up, then signal readiness before listeners open.

  5. 5
    Stop in reverse

    Revoke subscriptions and commands, unload instances, then stop runtimes.

Any partial startup failure rolls back loaded instances, event subscriptions, command trees and runtime processes.

Go registers callbacks in OnLoad, followed by OnEnable and OnDisable. Java receives Host through its constructor and registers listeners in enable(). Unloading detaches subscriptions; runtime recovery creates fresh plugin state. Persist durable data in the plugin's data directory, not only in memory.

R1

abi/v1

The language-neutral values carried across runtime boundaries.

Value

Tagged language-neutral values: booleans, integers, doubles, strings, bytes and recursive lists. Generated native event types hide this positional transport from normal Go and Java handlers.

ABI source ↗

Event definition

The schema declares event fields, their allowed mutations and cancellation. Code generation keeps host, Go and Java representations aligned.

Verdict

A handler returns cancellation, permitted mutations and queued effects through the existing IPC verdict. No extra mutation call is required.

Plugin-defined events

Providers declare events and records in their manifest or Java annotations. Subscribers generate their types from the provider bundle.

Layout locks

events.lock.json rejects field reordering or removal at package time. Appending a field remains compatible.

R2

Go author API

Import github.com/GoCraft-MC/gocraft-api-go as gocraft. Plugins do not import server internals. See the matching SDK source.

Plugin

OnLoad(gocraft.Context) error
OnEnable() error
OnDisable() error

Context

Provides Events(), Commands(), Scheduler(), Logger(), Metadata() and DataDirectory().

Typed listeners

OnPlayerChat and other generated registrations take typed pointers. Observational callbacks such as OnPlayerJoin have no control argument in this preview.

EventControl

Cancel() requests cancellation; Cancelled() reads its state. Only cancellable callbacks receive it.

CommandSet

Return a builder from Commands() to declare command paths, typed arguments, permissions and handlers together.

Run

Start the plugin with gocraft.Run(metadata, plugin). The executable handles its own IPC session and lifecycle.

R3

Java author API

Use fr.gocraft.api through the GoCraft Gradle plugin. The JVM repository contains the API, runtime, annotation processor and Gradle integration.

Plugin

Implement enable() and disable(). Accept Host in the constructor; no static main or Bukkit JavaPlugin is required.

Host

During enable, call host.registerListener(new ChatListener()). The manifest must declare every subscribed event.

@Subscribe

Receive a typed event. Add EventControl only for cancellable events and use generated setters for permitted mutations.

Commands

@Cmd, @Sub and @Permission feed generated command metadata. Builders bind the resulting paths to handlers.

Custom events

@PluginEvent declares a provider. host.emit(event) returns whether it was allowed; read the modified object after it returns.

Build tooling

gocraftBundle compiles, stages and packages the plugin. The Gradle plugin selects the API and processor together.

R4

Limits and guarantees

2 msdefault shared cancellable-event budget
60 ssliding plugin health window
>10%failure ratio that disables after 10 samples
4,096maximum nodes in one command tree
64maximum command tree depth
4 MiBmaximum IPC envelope size
Preview boundaries

Custom events, runtimes, SDKs, commands and packaging work today. Native gameplay coverage is still expanding: there is no separate PlayerKill event or structured killer data, and movement, portal/pearl teleports, creative inventory edits and specialised placement are not fully covered. Typed configuration, cross-plugin services and datastore APIs remain separate future work.

Something unclear?

API docs should be reviewable like code. Open an issue or bring the exact section to Discord.

Open an issue