Custom packets let you synchronise data between the server and clients. This tutorial uses Common Network, a multiloader networking library that lets you define, register, and send packets entirely from your common module - no loader-specific registration or send helpers required.
Side.SERVER instead and uses ctx.sender() to access the sending player.Gradle Setup
Add the BlameJared Maven repository and the Common Network dependency to each subproject. The common module uses the API-only artifact at compile time; the loader subprojects depend on their full runtime artifact, which pulls the common artifact in transitively:
Replace VERSION with the latest mod version from the Modrinth page. At the time of writing the latest 1.21.1 build is 1.0.21, giving a full dependency string of e.g. mysticdrew:common-networking-fabric:1.0.21-1.21.1.
Defining the Packet
Create a network package in your common module and add a plain class for your packet. The class does not implement CustomPacketPayload directly - the library wraps it internally before sending. You need four components: a channel identifier, a stream codec, encode/decode methods, and a static handler:
StreamCodec.ofMember takes two method references: the instance encode method and the decode constructor. Fields must be written and read in the same order - mismatching them will silently corrupt data. Minecraft.getInstance().execute() schedules the handler body onto the main game thread, which is required before touching any game state.
ctx.message() returns the packet instance. ctx.sender() returns the ServerPlayer when handling on the server side, and is null on the client side.Registering Packets
Create a registration class in your common module and call it from your common mod initialiser. A single Network.registerPacket call covers both loaders - no loader-specific event listeners or split client/server registration needed:
Network.registerPacket returns the Network instance, so you can chain registrations for multiple packets in one block:
Sending Packets
Use the Dispatcher static facade from your common module. No loader-specific helper classes or service-loader wiring is needed:
Dispatcher also provides spatial variants - sendToClientsInRange, sendToClientsLoadingChunk, and sendToClientsLoadingPos - for scoping delivery to nearby players. If you prefer instance-based calls, Network.getNetworkHandler() exposes the same methods on a NetworkHandler object.
Testing
Add a temporary block interaction handler that fires the packet when a player right-clicks your New Dirt block. Join a local world and right-click the block - you should see the chat message appear, confirming the packet was sent from the server and received on the client. If nothing appears, check the console for unregistered payload errors, which indicate PacketRegistration.init() was not called during startup.
You can see an example of this in the source code on GitHub below.
You can find the source for this tutorial here:
View Source on GitHubData Generation: Advancements (MultiLoader 1.21+)
Build an advancement tree with a root node and child advancements using AdvancementProvider.AdvancementGenerator, attach InventoryChangeTrigger and PlacedBlockTrigger criteria, and wire recipe unlock rewards.
Continue →