Hytale Server Plugin API: Events, Commands, and Permissions

A technical reference for the Hytale server plugin API covering the plugin lifecycle, EventBus system, command registration, and permission management.

Player Games7 min read

Hytale's server runs on Java and exposes a plugin API for extending gameplay, listening to events, registering commands, and managing permissions. Plugins are loaded from the mods/ directory and follow a well-defined lifecycle. This reference is based on Hytale server version 2026.03.26.

#Plugin Lifecycle

Every plugin extends JavaPlugin, which itself extends PluginBase. The server manages plugins through a state machine:

NONE → SETUP → START → ENABLED → SHUTDOWN → DISABLED

If any stage fails, the plugin transitions to FAILED and is not loaded.

Java
package com.example.myplugin;

import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class MyPlugin extends JavaPlugin {

    public MyPlugin(JavaPluginInit init) {
        super(init);
    }

    @Override
    protected void setup() {
        getLogger().info("Plugin setting up...");

        // Register event listeners
        getEventRegistry().register(
            PlayerConnectEvent.class,
            this::onPlayerConnect
        );

        // Register commands
        getCommandRegistry().register(new SpawnCommand());
    }

    @Override
    protected void start() {
        getLogger().info("Plugin started.");
    }

    @Override
    protected void shutdown() {
        getLogger().info("Plugin shutting down.");
    }
}

During setup(), you have access to several registries through PluginBase:

  • getEventRegistry() — register event listeners
  • getCommandRegistry() — register commands
  • getEntityRegistry() — register custom entity types
  • getTaskRegistry() — schedule recurring tasks
  • getAssetRegistry() — register custom assets
  • getEntityStoreRegistry() — register ECS components for entities
  • getChunkStoreRegistry() — register ECS components for chunks

The PluginSetupEvent fires when a plugin initializes, which other plugins can listen for if they need to coordinate loading order.

#The EventBus

Hytale's event system is built around the EventBus class. It supports both synchronous and asynchronous event handling with priority ordering.

#Registering a Sync Listener

Java
getEventRegistry().register(
    PlayerConnectEvent.class,
    event -> {
        PlayerRef player = event.getPlayerRef();
        World world = event.getWorld();
        getLogger().info("Player connected to " + world);
    }
);

#Priority Ordering

Listeners execute in priority order. Higher-priority listeners run first, which is useful when multiple plugins need to process the same event:

Java
getEventRegistry().register(
    EventPriority.HIGH,
    PlayerChatEvent.class,
    event -> {
        // This runs before normal-priority listeners
        String message = event.getMessage();
        getLogger().info("Chat: " + message);
    }
);

#Async Event Handling

Some events support asynchronous processing. Use registerAsync() when your handler needs to perform I/O or other blocking work without stalling the server tick:

Java
getEventRegistry().registerAsync(
    PlayerConnectEvent.class,
    future -> future.thenApply(event -> {
        // This runs on a separate thread
        // Load player data from a database, etc.
        return event;
    })
);

Note: Synchronous listeners (register) run on the server tick thread and block execution until they complete. Asynchronous listeners (registerAsync) run off the main thread and return a CompletableFuture. Use async for database lookups, HTTP calls, or anything that would otherwise lag the server.

#Cancellable Events

Many gameplay events extend CancellableEcsEvent, which lets you prevent the default behavior. For example, you can stop a player from placing a block:

Java
getEventRegistry().register(
    PlaceBlockEvent.class,
    event -> {
        if (event.isCancelled()) return; // Another plugin may have cancelled it

        Vector3i target = event.getTargetBlock();
        ItemStack item = event.getItemInHand();

        // Prevent placing blocks above Y=200
        if (target.getY() > 200) {
            event.setCancelled(true);
        }
    }
);

Warning: When handling a cancellable event, always check isCancelled() at the top of your listener. A higher-priority plugin may have already cancelled the event, and processing it again could cause unexpected behavior.

#Event Catalog

Here are the key server events available in version 2026.03.26:

#Player Events

EventCancellableDescription
PlayerConnectEventNoFired when a player connects. Provides PlayerRef, Holder<EntityStore>, and optional World.
PlayerDisconnectEventNoFired on disconnect. Includes DisconnectReason.
PlayerReadyEventNoFired when the player is fully loaded and ready to play.
PlayerChatEventNoFired when a player sends a chat message. Provides the message text.
PlayerMouseButtonEventNoFired on mouse input. Provides item in hand, target block/entity, and button type.
PlayerInteractEventYesFired when a player interacts with a block or entity.
PlayerCraftEventNoFired when a player crafts an item.

#Block and Item Events

EventCancellableDescription
PlaceBlockEventYesFired when a block is placed. Provides ItemStack, target position (Vector3i), and RotationTuple.
BreakBlockEventYesFired when a block is broken. Provides ItemStack, target position, and BlockType.
DamageBlockEventYesFired when a block takes damage (before breaking).
UseBlockEvent.PreYesFired before a block interaction. Includes InteractionType and InteractionContext.
UseBlockEvent.PostNoFired after a block interaction completes.
DropItemEventYesFired when an item is dropped. Subtypes: Drop and PlayerRequest.
InteractivelyPickupItemEventYesFired when a player picks up an item from the world.

#Crafting and Discovery Events

EventCancellableDescription
CraftRecipeEvent.PreYesFired before a recipe is crafted. Provides CraftingRecipe and quantity.
CraftRecipeEvent.PostNoFired after crafting completes.
DiscoverZoneEvent.DisplayYesFired when a zone discovery UI would display. Control visibility with setDisplay().

#Server Events

EventCancellableDescription
BootEventNoFired when the server starts.
ShutdownEventNoFired when the server is shutting down.
PrepareUniverseEventNoFired when the universe is being prepared.
PluginSetupEventNoFired when a plugin completes setup.

#Permission Events

EventCancellableDescription
PlayerPermissionChangeEventNoFired when a player's permissions change.
PlayerGroupEvent.AddedNoFired when a player is added to a permission group.
PlayerGroupEvent.RemovedNoFired when a player is removed from a permission group.
GroupPermissionChangeEventNoFired when a group's permissions are modified.

#Command Registration

Commands are defined by extending one of the AbstractCommand subclasses:

  • AbstractPlayerCommand — requires a player sender
  • AbstractWorldCommand — requires a world context
  • AbstractAsyncCommand — executes asynchronously
  • AbstractTargetPlayerCommand — targets another player
  • AbstractTargetEntityCommand — targets an entity
Java
import com.hypixel.hytale.server.core.command.AbstractPlayerCommand;
import com.hypixel.hytale.server.core.command.CommandContext;

public class HealCommand extends AbstractPlayerCommand {

    public HealCommand() {
        super("heal");
        setPermission("myplugin.command.heal");
        setDescription("Restore full health");
    }

    @Override
    protected void execute(CommandContext context) {
        // Heal the player who ran the command
        PlayerRef player = context.getPlayerRef();
        // ... apply healing logic
        context.sendMessage("You have been healed.");
    }
}

Register the command in your plugin's setup() method:

Java
@Override
protected void setup() {
    getCommandRegistry().register(new HealCommand());
}

Commands support typed arguments, optional flags, and subcommand hierarchies. Permission strings are auto-generated from the command path (e.g., plugin.command.heal), but you can also set them explicitly.

#Permissions

Hytale's permission system uses dot-notation strings under the hytale.* namespace. The PermissionProvider interface lets plugins define custom permission backends:

Java
// Check a permission on a player
if (player.hasPermission("myplugin.feature.fly")) {
    // Enable flight
}

Built-in permission namespaces include:

  • hytale.command.* — auto-generated from registered commands
  • hytale.editor.* — editor tools and builder brushes
  • hytale.camera.* — camera modes like flycam
  • hytale.world_map.teleport.* — map teleportation

Players can be added to permission groups, and changes fire PlayerPermissionChangeEvent and PlayerGroupEvent so other plugins can react.

#Putting It Together

Here's a complete example: a plugin that welcomes players on connect and provides a /spawn command.

Java
package com.example.welcomeplugin;

import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
import com.hypixel.hytale.server.core.command.AbstractPlayerCommand;
import com.hypixel.hytale.server.core.command.CommandContext;
import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;

public class WelcomePlugin extends JavaPlugin {

    public WelcomePlugin(JavaPluginInit init) {
        super(init);
    }

    @Override
    protected void setup() {
        getEventRegistry().register(
            PlayerConnectEvent.class,
            event -> {
                getLogger().info(
                    "Player " + event.getPlayerRef() + " connected."
                );
            }
        );

        getCommandRegistry().register(new SpawnCommand());
    }

    private static class SpawnCommand extends AbstractPlayerCommand {

        SpawnCommand() {
            super("spawn");
            setPermission("welcome.command.spawn");
            setDescription("Teleport to world spawn");
        }

        @Override
        protected void execute(CommandContext context) {
            Teleport teleport = Teleport.createForPlayer(
                new Vector3d(0, 64, 0),
                new Vector3f(0, 0, 0)
            );
            // Apply teleport component to the player entity
            context.sendMessage("Teleporting to spawn...");
        }
    }
}

#Plugin development checklist

  • Extend JavaPlugin and implement setup(), start(), and shutdown()
  • Register event listeners in setup() via getEventRegistry()
  • Register commands in setup() via getCommandRegistry()
  • Check isCancelled() before processing cancellable events
  • Use registerAsync() for listeners that perform I/O
  • Set explicit permissions on commands
  • Place your compiled JAR in the mods/ directory
  • Test on a local server before deploying

Build it yourself

Turn an idea into a working Hytale mod

Describe what you want in plain English and get a real, installable build. No boilerplate, no setup.

Start building

Related guides