Write Minecraft Bukkit plugins in Python. Zero Java required.
Toomo lets you write Bukkit/Spigot/Paper plugins using pure Python, powered by GraalPy (GraalVM's Python runtime). Define event handlers with decorators, register commands, and access the full Bukkit API — all without touching a single line of Java.
from toomo import ToomoPlugin, on_event, on_command
from toomo.events import PLAYER_JOIN, PLAYER_QUIT
class MyPlugin(ToomoPlugin):
def on_enable(self):
self.logger.info("Hello from Python!")
@on_event(PLAYER_JOIN)
def on_join(self, event):
player = event.getPlayer()
player.sendMessage(f"Welcome, {player.getName()}!")
@on_command("hello")
def hello(self, sender, cmd, label, args):
sender.sendMessage("Hello from Python!")
return TrueYour Python code ──→ toomo build ──→ plugin.jar
│
┌──────────────┼──────────────┐
│ Java Bootstrap (auto-generated) │
│ GraalPy Runtime (bundled) │
│ Bukkit Bindings (auto-wired) │
└─────────────────────────────────┘
- You write Python using toomo's decorators and bindings
toomo buildcompiles a thin Java bootstrap + bundles your Python- Drop the JAR into your server's
plugins/folder - Server runs GraalVM — Python executes natively on the JVM
- Development machine: Python 3.11+, uv
- Minecraft server: GraalVM JDK 23+ with GraalPy
pip install toomo
# or
uv tool install toomotoomo init MyFirstPlugin
cd MyFirstPluginThis creates:
MyFirstPlugin/
├── toomo.toml # Plugin configuration
├── plugin.py # Your plugin code
└── .gitignore
Edit toomo.toml:
[plugin]
name = "MyFirstPlugin"
version = "1.0.0"
api_version = "1.21"
main = "plugin"
description = "My first Python Bukkit plugin"
[[commands]]
name = "hello"
description = "Says hello"
usage = "/hello"
[[permissions]]
name = "myplugin.use"
description = "Allows using myplugin"
default = "true"toomo buildOutput: build/MyFirstPlugin-1.0.0.jar
- Make sure your Minecraft server runs on GraalVM JDK 23+
- Copy the JAR to your server's
plugins/folder - Start/restart the server
| Section | Key | Type | Description |
|---|---|---|---|
[plugin] |
name |
string | Plugin name |
[plugin] |
version |
string | Plugin version |
[plugin] |
api_version |
string | Bukkit API version (e.g., "1.21") |
[plugin] |
main |
string | Python module name (default: "plugin") |
[plugin] |
description |
string | Plugin description |
[[commands]] |
name |
string | Command name (without /) |
[[commands]] |
description |
string | Command description |
[[commands]] |
usage |
string | Usage hint |
[[commands]] |
aliases |
string[] | Command aliases |
[[commands]] |
permission |
string | Required permission |
[[permissions]] |
name |
string | Permission node |
[[permissions]] |
description |
string | Permission description |
[[permissions]] |
default |
string | Default value (true/false/op) |
depend |
string[] | Hard dependencies | |
soft_depend |
string[] | Soft dependencies |
Base class for all plugins. Provides:
| Method/Property | Description |
|---|---|
on_enable() |
Called when plugin loads |
on_disable() |
Called when plugin unloads |
.logger |
Java Logger instance |
.server |
Bukkit Server instance |
.name |
Plugin name |
.data_folder |
Plugin data directory |
.get_config() |
Get config.yml |
.save_config() |
Save default config |
.save_resource(path) |
Extract resource from JAR |
@on_event('org.bukkit.event.player.PlayerJoinEvent')
def handler(self, event):
# event is a Bukkit Event object — use Java methods directly
player = event.getPlayer()Common events available as constants in toomo.events:
| Constant | Event |
|---|---|
PLAYER_JOIN |
PlayerJoinEvent |
PLAYER_QUIT |
PlayerQuitEvent |
PLAYER_CHAT |
AsyncPlayerChatEvent |
PLAYER_MOVE |
PlayerMoveEvent |
PLAYER_DEATH |
PlayerDeathEvent |
BLOCK_BREAK |
BlockBreakEvent |
BLOCK_PLACE |
BlockPlaceEvent |
ENTITY_DAMAGE |
EntityDamageEvent |
| ... | (80+ event constants) |
@on_command('commandname')
def handler(self, sender, command, label, args):
# sender: CommandSender (Player or ConsoleCommandSender)
# command: Command object
# label: string used to invoke the command
# args: list of strings
return True # Return True to indicate successAccess any Bukkit/Java class directly via GraalPy:
# From within your plugin methods:
Bukkit = java.type('org.bukkit.Bukkit')
worlds = Bukkit.getWorlds()
Material = java.type('org.bukkit.Material')
diamond = Material.DIAMOND
# Or use the convenience wrapper:
from toomo import bukkit
bukkit.Bukkit.broadcastMessage("Hello!")- Python 3.11+
- uv (recommended)
- GraalVM JDK 23+ with GraalPy
# Install GraalVM + GraalPy gu install python - Paper/Spigot server for Minecraft 1.21+
GraalPy runs Python code directly on the JVM with full access to Java classes and libraries. This means your Python plugin can interact with the Bukkit API as if it were native Java — no bridges, no RPC, no performance penalty.
MIT