Skip to content

Quick Start ​

Pumpkin plugins can be written in Zig with the pumpkin-api-zig bindings. A plugin is compiled to a WebAssembly component, which the server loads from its plugins folder.

The bindings are generated from the plugin API, so everything the API offers is available from Zig. On top of that there are helpers for the things almost every plugin does: listening to events, declaring commands, building text, showing menus, handing out items, scheduling work, reading config files, keeping player data and talking to the network.

Prerequisites ​

You need Zig 0.16.0 and wasm-tools in your PATH. The build uses wasm-tools to attach the plugin API to your code and to package it as a component.

Setting up a project ​

Create a directory for the plugin and add a release of the bindings as a dependency:

bash
mkdir my-zig-plugin
cd my-zig-plugin
zig fetch --save git+https://github.com/Pumpkin-MC/pumpkin-api-zig#v0.1.0

The releases list every version. To update, run the command again with the new one.

Then create build.zig:

zig
const std = @import("std");
const pumpkin_api = @import("pumpkin_api_zig");

pub fn build(b: *std.Build) void {
    _ = pumpkin_api.addPlugin(b, b.dependency("pumpkin_api_zig", .{}), .{
        .name = "my-zig-plugin",
        .root_source_file = b.path("src/main.zig"),
    });
}

addPlugin compiles src/main.zig for wasm32-wasi, makes the API available to it as the pumpkin module and turns the result into a component.

If you are working on the bindings themselves, you can depend on a local checkout instead. The path is relative to your project:

zig
.dependencies = .{
    .pumpkin_api_zig = .{ .path = "../pumpkin-api-zig" },
},

Writing the plugin ​

Put the following in src/main.zig:

zig
const std = @import("std");
const pumpkin = @import("pumpkin");

pub const std_options: std.Options = .{ .logFn = pumpkin.logFn };

const MyPlugin = struct {
    pub const metadata: pumpkin.Metadata = .{
        .name = "my-zig-plugin",
        .version = "0.1.0",
        .authors = &.{"You"},
        .description = "An example plugin in Zig.",
    };

    pub const events = .{
        .player_join_event = onJoin,
    };

    pub fn onLoad(_: pumpkin.Context) !void {
        std.log.info("Hello from Zig!", .{});
    }

    fn onJoin(_: pumpkin.Server, ev: *pumpkin.EventData(.player_join_event)) void {
        std.log.info("{s} joined", .{ev.player.getName()});
    }
};

comptime {
    pumpkin.register(MyPlugin);
}

A plugin is a struct handed to pumpkin.register, which generates everything the server needs to call into it. The struct's declarations describe the plugin. metadata is the only one that's required: it holds the name, version, authors and description the server shows, and can also list dependencies on other plugins and the permissions the plugin needs, which Files and Configuration covers. events lists the events to listen to. onLoad runs after the plugin's events and commands have been registered, and onUnload runs before it is unloaded.

Setting std_options.logFn to pumpkin.logFn sends std.log output to the server console, with the scope in front if you use std.log.scoped.

Everything the API gives you, the event's player included, only lives until the callback returns. Handles and Memory explains how to keep things for longer.

Building and running ​

bash
zig build
cp zig-out/my-zig-plugin.wasm /path/to/pumpkin/plugins/

Start or restart the server, and the log should show the plugin's message:

text
[INFO] Hello from Zig!

WARNING

Each release of the bindings names the revision of the plugin API it was built against. A server older than that refuses the plugin with Plugin was built against a different iteration of the API, and updating the server fixes it.

Examples ​

The bindings repository has a small plugin for each part of the API. Building the repository with zig build builds them all into zig-out/.

ExampleShows
helloEvents, a command and styled text
healCommand arguments read with cmd.typed
menuAn inventory menu with buttons
itemsBuilding, editing and recognizing items, and giving and taking them
tasksDelayed and repeating tasks that carry a value
configA TOML config file that can be reloaded
storeData kept for every player in a file of its own
httpsWeb requests with pumpkin.http
tcp-serverA TCP server that bridges the chat
udpReceiving and answering UDP datagrams
dnsLooking up the addresses of a host name
httpA TCP connection with a timeout, sending a plain HTTP request
nettestRuns every networking path with /nettest and reports each one

Released under the MIT License.