Back to Knowledge Base

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 Games··hytale

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.

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

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:

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:

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:

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
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:

@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:

// 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.

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