A powerful, language-agnostic plugin system for Dragonfly Minecraft Bedrock servers using gRPC and Protocol Buffers.
| Benefit | Description | Use Case |
|---|---|---|
| 🌍 Any Language | Write plugins in JavaScript, TypeScript, PHP, Python, Rust, C++, or any language with gRPC support | Use the language your team knows best |
| 💰 Sell Plugins | Compile to binary (Rust, Go, C++) and distribute without source code | Create commercial plugins |
| 🔥 Hot Reload | Edit JS/TS/PHP plugins and see changes instantly - no server restart needed | Develop and debug plugins in real-time |
| 📱 Remote Control | Plugins connect over gRPC - run them anywhere (phone app, cloud service, discord bot) | Build mobile admin apps |
| 📦 Use Any Library | Import npm packages on a Go server, use Python ML libraries, etc. | Leverage entire ecosystems |
| ⚡ Zero Performance Impact | Plugins run in separate processes - slow/heavy plugin code doesn't affect server TPS | Run intensive tasks without lag |
| 🚀 High Performance (COMING SOON) | Optimized protobuf protocol with optional batching for low latency | Handle 100+ players with movement events |
| 🔒 Sandboxing | Control what plugins can access via gRPC permissions | Host untrusted plugins safely |
# Hot reload: Edit plugin code while server is running
vim plugins/my-plugin.js # Make changes
# Changes apply immediately - no restart!
# Remote plugin: Control server from your phone
# Plugin runs on your phone, connects to server over internet
phone-app → [gRPC] → Dragonfly Server
# Binary plugin: Sell without source code
rustc plugin.rs --release # Compile to binary
# Distribute the binary - customers can't see your code- Multi-Language Support: Write plugins in JavaScript, TypeScript, PHP, Python, Rust, C++, or any language with gRPC support
- Event-Driven Architecture: Subscribe to specific events (player join, chat, block break, etc.)
- Type Safety: Generated types for TypeScript and other statically typed languages
git clone https://github.com/secmc/plugin.git
cd plugingo mod downloadEdit plugins/plugins.yaml:
plugins:
- id: my-plugin
name: My First Plugin
command: "node"
args: ["examples/plugins/node/hello.js"]
address: "127.0.0.1:50051"go run main.goYour plugin will automatically connect and start receiving events!
We provide complete working examples in multiple languages:
- TypeScript - Type-safe plugin with generated types (recommended for production)
- Node.js - Simple JavaScript plugin
- PHP - PHP plugin using gRPC extension
See examples/plugins/README.md for detailed documentation and more examples.
<?php
// Example plugin showing command handling and block break event modification
require_once __DIR__ . '/vendor/autoload.php';
use Grpc\ChannelCredentials;
$pluginId = 'my-plugin';
$address = '127.0.0.1:50051';
$client = new \Df\Plugin\PluginClient($address, [
'credentials' => ChannelCredentials::createInsecure(),
]);
$stream = $client->EventStream();
try {
foreach ($stream->responses() as $message) {
// Handle handshake
if ($message->hasHello()) {
$hello = new \DF\Plugin\PluginToHost();
$hello->setPluginId($pluginId);
$pluginHello = new \DF\Plugin\PluginHello();
$pluginHello->setName('My Plugin');
$pluginHello->setVersion('1.0.0');
$pluginHello->setApiVersion($message->getHello()->getApiVersion());
// Register /mine command
$command = new \DF\Plugin\CommandSpec();
$command->setName('/mine');
$command->setDescription('Get mining boost');
$pluginHello->setCommands([$command]);
$hello->setHello($pluginHello);
$stream->write($hello);
// Subscribe to events
$sub = new \DF\Plugin\PluginToHost();
$sub->setPluginId($pluginId);
$subscribe = new \DF\Plugin\EventSubscribe();
$subscribe->setEvents([
\DF\Plugin\EventType::PLAYER_JOIN,
\DF\Plugin\EventType::COMMAND,
\DF\Plugin\EventType::PLAYER_BLOCK_BREAK,
]);
$sub->setSubscribe($subscribe);
$stream->write($sub);
continue;
}
if ($message->hasEvent()) {
$event = $message->getEvent();
// Handle /mine command
if ($event->getType() === \DF\Plugin\EventType::COMMAND && $event->hasCommand()) {
$cmd = $event->getCommand();
if ($cmd->getCommand() === 'mine') {
// Send message to player
$action = new \DF\Plugin\Action();
$send = new \DF\Plugin\SendChatAction();
$send->setTargetUuid($cmd->getPlayerUuid());
$send->setMessage('§6⛏️ Mining boost activated! Break blocks for double drops!');
$action->setSendChat($send);
$batch = new \DF\Plugin\ActionBatch();
$batch->setActions([$action]);
$resp = new \DF\Plugin\PluginToHost();
$resp->setPluginId($pluginId);
$resp->setActions($batch);
$stream->write($resp);
}
// Acknowledge event
$result = new \DF\Plugin\EventResult();
$result->setEventId($event->getEventId());
$result->setCancel(false);
$resp = new \DF\Plugin\PluginToHost();
$resp->setPluginId($pluginId);
$resp->setEventResult($result);
$stream->write($resp);
}
// Handle block break with double drops
if ($event->getType() === 'BLOCK_BREAK' && $event->hasBlockBreak()) {
$blockBreak = $event->getBlockBreak();
echo "[php] {$blockBreak->getName()} broke block at ";
echo "{$blockBreak->getX()},{$blockBreak->getY()},{$blockBreak->getZ()}\n";
// Give double drops for every 10th block (X coordinate % 10 == 0)
if ($blockBreak->getX() % 10 === 0) {
$drop = new \DF\Plugin\ItemStack();
$drop->setName('minecraft:diamond');
$drop->setCount(2);
$drop->setMeta(0);
$mutation = new \DF\Plugin\BlockBreakMutation();
$mutation->setDrops([$drop]);
$mutation->setXp(10);
$result = new \DF\Plugin\EventResult();
$result->setEventId($event->getEventId());
$result->setBlockBreak($mutation);
$resp = new \DF\Plugin\PluginToHost();
$resp->setPluginId($pluginId);
$resp->setEventResult($result);
$stream->write($resp);
} else {
// Acknowledge normally
$result = new \DF\Plugin\EventResult();
$result->setEventId($event->getEventId());
$result->setCancel(false);
$resp = new \DF\Plugin\PluginToHost();
$resp->setPluginId($pluginId);
$resp->setEventResult($result);
$stream->write($resp);
}
}
}
}
} catch (Exception $e) {
echo "[php] Error: " . $e->getMessage() . "\n";
} finally {
$stream->writesDone();
}
echo "[php] plugin connected to {$address}\n";dragonfly-plugins/
├── dragonfly/ # Modified Dragonfly server with plugin support
├── plugin/ # Plugin system core
│ ├── proto/ # Protocol Buffer definitions
│ ├── manager.go # Plugin lifecycle management
│ └── README.md # Plugin system documentation
├── examples/
│ └── plugins/ # Example plugins in various languages
├── plugins/
│ └── plugins.yaml # Plugin configuration
└── main.go # Server entry point
┌─────────────────┐ gRPC Stream ┌──────────────────┐
│ │ ←──────────────────────────→ │ │
│ Dragonfly │ Events: JOIN, CHAT, etc. │ Your Plugin │
│ Server │ Actions: TELEPORT, etc. │ (Any Language) │
│ (Go) │ │ │
└─────────────────┘ └──────────────────┘
- Server starts and loads plugin configuration from
plugins/plugins.yaml - Plugin process launches via configured command (e.g.,
node plugin.js) - Handshake occurs where plugin registers capabilities
- Plugin subscribes to events it wants to receive
- Events flow from server to plugin in real-time
- Plugin executes actions by sending messages back to server
- Plugin Examples - Complete guide to example plugins
- Plugin System - Core plugin system documentation
- Protocol Buffer Definitions - API reference
- Plugin Architecture - Design documentation
to generate our protobuf types, you will need to install buf and protoc-gen-go:
# Install buf
# Follow instructions at https://buf.build/docs/cli/installation/
# Install protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latestThen run:
make proto