ProtocolLib integration

ProtocolLib is a powerful Minecraft plugin that allows other plugins to intercept, read, and modify packets sent between the server and client.

While it's technically possible to access ProtocolLib directly via raw Javascript in scripts, this is not recommended. Unofficial use results in:

  • Memory leaks

  • Script instability

  • Limitations due to Java-JS translation

OpenJS provides official support for ProtocolLib, exposing a clean and stable API.


Enabling ProtocolLib Support


API Overview

ProtocolLib.registerListener(priority, handler, packetTypes)

Registers a new packet listener.

  • priority: "LOW", "NORMAL", "HIGH", or "MONITOR"

  • handler: JavaScript object containing onSend(event) and/or onReceive(event)

  • packetTypes: Array of packet names, e.g., "Play.Server.BLOCK_CHANGE"

Returns: a listener object

listener = ProtocolLib.registerListener("NORMAL", {
  onSend(event) {
    const packet = event.getPacket();
    // handle packet here
  }
}, ["Play.Server.BLOCK_CHANGE"]);

Additional info on packetTypes:

The packetType is specified relative to the com.comphenix.protocol.PacketType class. For example, Play.Server.BLOCK_CHANGE corresponds to com.comphenix.protocol.PacketType.Play.Server.BLOCK_CHANGE. All provided packet types follow this structure and are based on their original class path.


ProtocolLib.unregisterListener(listener)

Unregisters a previously registered packet listener.

ProtocolLib.unregisterListener(listener);

Packet Event Object

The event passed into your handler (onSend / onReceive) provides access to:

Method
Description

getPacket()

Returns the actual ProtocolLib packet

getPacketType()

Returns packet type as enum (e.g. Play.Server.CHAT)

You can use:

  • read(index) / write(index, value) to access fields

  • Packet-specific methods like getBlockData() or getBlockDataArrays()


Example Script: Block Spoofing

This script replaces all outgoing non-air blocks with a spoofed OAK_LOG block:

//!import org.bukkit.Material
//!import com.comphenix.protocol.wrappers.WrappedBlockData
//!ProtocolLib

const Material = org.bukkit.Material;
const createDataMethod = getMethod(WrappedBlockData, "createData", ["org.bukkit.Material"]);

let SpoofedBlock = createDataMethod.invoke(null, [Material.OAK_LOG]);

const handler = {
  onSend(event) {
    const packet = event.getPacket();
    const type = event.getPacketType().name();

    if (type === "BLOCK_CHANGE") {
      const blockData = packet.getBlockData().read(0);
      if (!blockData.getType().equals(Material.AIR)) {
        packet.getBlockData().write(0, SpoofedBlock);
      }
    }

    if (type === "MULTI_BLOCK_CHANGE") {
      let blocks = packet.getBlockDataArrays().read(0);
      for (let i = 0; i < blocks.length; i++) {
        if (!blocks[i].getType().equals(Material.AIR)) {
          blocks[i] = SpoofedBlock;
        }
      }
      packet.getBlockDataArrays().write(0, blocks);
    }
  }
};

const listener = ProtocolLib.registerListener("NORMAL", handler, [
  "Play.Server.BLOCK_CHANGE",
  "Play.Server.MULTI_BLOCK_CHANGE"
]);

Best Practices

  • Unregister unused listeners with ProtocolLib.unregisterListener(...)

  • Avoid modifying packets in "MONITOR" priority (read-only phase)

  • Use getMethod() and invoke() carefully for wrapper class access


Last updated

Was this helpful?