Switchboard

Software, photography, music, and more.

SimpleChestLogging

Posted 07/04/2026 by Tim Northrop

I wrote a very simple Minecraft Paper plugin that logs every player interaction with vanilla containers (barrels, chests, copper chests, ender chests, shulker boxes, dispensers, droppers, and hoppers) to the server log. This is useful for small to medium servers that want to keep track of items without the overhead of a full logging/anti-cheat plugin.

You can download the .jar from the plugin's CurseForge page to add it to your server. Find the source code on the GitHub repository.

The plugin's functionality is extremely simple. It takes a snapshot of the container's contents on every InventoryClickEvent and then compares the snapshot to the container's contents during tick N + 1, when the changes from the event have been applied. The logic for taking the difference between the two snapshots and logging it can be seen here:

    ...
    private void diffAndLog(ItemStack[] before, ItemStack[] after, Inventory chest, Player player) {
        Map<ItemType, Integer> diffs = new HashMap<>();

        String chestType = chest.getType().toString();
        Location chestLoc = chest.getLocation();
        if (chestLoc == null) {
            return;
        }
        String chestLocStr = "(" + chestLoc.getBlockX()
                + ", " + chestLoc.getBlockY()
                + ", " + chestLoc.getBlockZ() + ")";

        for (int i = 0; i < before.length; i++) {
            ItemStack bef = before[i];
            ItemStack aft = after[i];

            if (isEmpty(bef) && isEmpty(aft)) {
                continue;
            }

            if (isEmpty(bef)) {
                addToDiff(aft.getType().asItemType(), aft.getAmount(), diffs);
                continue;
            }
            if (isEmpty(aft)) {
                addToDiff(bef.getType().asItemType(), (-bef.getAmount()), diffs);
                continue;
            }

            if (bef.getType().equals(aft.getType()) && bef.getAmount() == aft.getAmount()) {
                continue;
            }

            addToDiff(aft.getType().asItemType(), aft.getAmount(), diffs);
            addToDiff(bef.getType().asItemType(), (-bef.getAmount()), diffs);

        }

        diffs.forEach((k, v) -> {
            if (v > 0) {
                plugin.getLogger().info(player.getName() + " put " + v + "x " + k.getKey().getKey()
                        + " in " + chestType + " at " + chestLocStr);
            } else if (v < 0) {
                plugin.getLogger().info(player.getName() + " took " + (-v) + "x " + k.getKey().getKey()
                        + " from " + chestType + " at " + chestLocStr);
            }
        });
    }
    ...

The plugin also handles InventoryDragEvent, which is actually easier, as the event provides the raw slot IDs of the slots that will be changed by the event. The handler for these events can be seen here:

    ...
    @EventHandler
    public void onInventoryDrag(InventoryDragEvent event) {
        if (!(event.getWhoClicked() instanceof Player player) || plugin.isOff()) {
            return;
        }

        Inventory chest = event.getView().getTopInventory();
        if (chest.getSize() <= 0 || isNotContainer(chest.getType())) {
            return;
        }

        Map<Integer, ItemStack> newItems = event.getNewItems();
        if (newItems.isEmpty()) {
            return;
        }

        ItemStack[] before = new ItemStack[chest.getSize()];
        ItemStack[] after = new ItemStack[chest.getSize()];
        newItems.forEach((k, v) -> {
            if (k < chest.getSize()) {
                after[k] = v;
            }
        });

        diffAndLog(before, after, chest, player);
    }
    ...

SimpleChestLogging requires no configuration and can be dropped into the plugins folder of any Paper server on a stable release for versions 1.21.11 through 26.2 of Minecraft.

Available Commands:

- /simplecl on: requires operator - turns SimpleChestLogging on

- /simplecl off: requires operator - turns SimpleChestLogging off

And that's it! Chest interactions will show up in the logs in the following format:

Notch put 3x iron_ingot in CHEST at (-640, 63, 216)
Notch took 64x cooked_beef from SHULKER_BOX at (-88, 65, -192)

- Tim