fabric-networking
Skill4 starsChanged 13 days agoMITView on GitHub
Send data between Minecraft client and server in a Fabric mod using the current payload API - CustomPacketPayload records, StreamCodec, PayloadTypeRegistry, ServerPlayNetworking / ClientPlayNetworking, PlayerLookup - with server-side validation and strict client/server separation. Use when adding a custom packet, syncing state to clients, sending input to the server, or fixing "unknown payload", disconnects or desyncs.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- PSB1234/Fabric-Minecraft-Skill-1.20.1their own version, first seen Jul 4, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- PSB1234/Fabric-Minecraft-Skill-1.20.1★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository10 other files it gives its agents.
- checkSkill
- fabric-client-renderingSkill
- fabric-config-and-keybindsSkill
- fabric-crash-debuggingSkill
- fabric-mixinsSkill
- fabric-project-setupSkill
- fabric-releaseSkill
- fabric-version-portingSkill
- newSkill
- portSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 4 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 134
Never trust ids, positions, counts, slot indexes, entity ids, strings or NBT from a client.
- Very specific: line 135
Check the entity still exists (level().getEntity(id)), is the right type, and is within interaction range (closerThan, distanceToSqr).
- Very specific: line 137
Check permissions for anything admin-like (the vanilla permission check changed shape in recent versions - look up how ServerPlayer exposes …
- Distinctive: line 139
Check the chunk is loaded before reading blocks - loading chunks on request is a DoS vector.
- Very specific: line 181
Prefer tracking/around over all: fewer packets, and players far away should not learn about things they cannot see.
- Distinctive: line 198
Read the docs page for your version before using it - this area changed in several releases.
- Distinctive: line 209
No server-side validation: trusting a client-sent position, entity id, amount, or "isCreative" flag.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Fabric networking
There is always a server, even in singleplayer (the integrated server). Anything the client wants to change in the world goes client -> server as a request; the server decides, applies, and tells the clients what happened.
Step 0 - Which API generation?
| Minecraft | API | Notes |
| 26.1+ | CustomPacketPayload + StreamCodec; PayloadTypeRegistry.clientboundPlay() / serverboundPlay() | Mojang names throughout |
| 1.20.5 - 1.21.11 | Same payload/codec design. Registry methods playS2C() / playC2S(). Yarn names: CustomPayload, PacketCodec, PacketCodecs, RegistryByteBuf, CustomPayload.Id | Mojang-mapped 1.21.x uses Mojang names with the playS2C/playC2S methods |
| 1.20.4 and older | Channel Identifier + PacketByteBuf (ServerPlayNetworking.send(player, id, buf)) | Legacy - do not use this shape on 1.20.5+ |
Detect it from minecraft_version in gradle.properties, then confirm the method names in
Fabric API's sources (./gradlew genSources, or open PayloadTypeRegistry in the IDE).
Version-matched docs: https://docs.fabricmc.net/develop/networking
Examples below use the 26.1+ names.
1. Define the payload (common code, src/main)
A payload is a record implementing CustomPacketPayload, with a TYPE and a StreamCodec.
package com.example.examplemod.network;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import com.example.examplemod.ExampleMod;
/** Client -> server: "please ping this block". */
public record PingBlockPayload(BlockPos pos, int colorIndex) implements CustomPacketPayload {
public static final Type<PingBlockPayload> TYPE = new Type<>(ExampleMod.id("ping_block"));
public static final StreamCodec<RegistryFriendlyByteBuf, PingBlockPayload> CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, PingBlockPayload::pos,
ByteBufCodecs.VAR_INT, PingBlockPayload::colorIndex,
PingBlockPayload::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}/** Server -> client: "show a ping at this position". */
public record ShowPingPayload(BlockPos pos, int colorIndex, UUID from) implements CustomPacketPayload {
public static final Type<ShowPingPayload> TYPE = new Type<>(ExampleMod.id("show_ping"));
public static final StreamCodec<RegistryFriendlyByteBuf, ShowPingPayload> CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, ShowPingPayload::pos,
ByteBufCodecs.VAR_INT, ShowPingPayload::colorIndex,
UUIDUtil.STREAM_CODEC, ShowPingPayload::from,
ShowPingPayload::new
);
@Override public Type<? extends CustomPacketPayload> type() { return TYPE; }
}Codec tips:
- Primitives:
ByteBufCodecs.BOOL,VAR_INT,INT,FLOAT,DOUBLE,STRING_UTF8(preferByteBufCodecs.stringUtf8(maxLength)to cap untrusted strings). - Vanilla types ship their own:
BlockPos.STREAM_CODEC,ItemStack.STREAM_CODEC,ComponentSerialization.STREAM_CODEC,UUIDUtil.STREAM_CODEC. - Collections:
ByteBufCodecs.collection(ArrayList::new, elementCodec, maxSize). StreamCodec.compositehas overloads up to a fixed number of fields; beyond that, nest a record or write the codec withStreamCodec.of(encoder, decoder).- Payload ids live in your mod's namespace (
examplemod:ping_block). Never reuse vanilla ids.
2. Register payload types (common initializer - both sides)
@Override
public void onInitialize() {
PayloadTypeRegistry.serverboundPlay().register(PingBlockPayload.TYPE, PingBlockPayload.CODEC);
PayloadTypeRegistry.clientboundPlay().register(ShowPingPayload.TYPE, ShowPingPayload.CODEC);
ServerPlayNetworking.registerGlobalReceiver(PingBlockPayload.TYPE, ServerPings::handle);
}Type registration must happen in the common ModInitializer, so both the client and the
dedicated server know the payload. Registering only in the client initializer gives
"unknown custom payload" / disconnects on servers.
3. Receive on the server - validate everything
(PingColors and RateLimiter stand for your own mod's classes.)
public final class ServerPings {
private static final double MAX_DISTANCE = 64.0;
public static void handle(PingBlockPayload payload, ServerPlayNetworking.Context context) {
ServerPlayer player = context.player();
BlockPos pos = payload.pos();
// 1. Validate ranges and enums - the client can send ANY bytes.
if (payload.colorIndex() < 0 || payload.colorIndex() >= PingColors.COUNT) return;
// 2. Validate world state: loaded chunk, distance, permissions.
if (!player.level().isLoaded(pos)) return;
if (player.distanceToSqr(Vec3.atCenterOf(pos)) > MAX_DISTANCE * MAX_DISTANCE) return;
// 3. Rate-limit anything a player can spam.
if (!RateLimiter.tryAcquire(player.getUUID())) return;
// 4. Act, then broadcast only to players who need it.
ShowPingPayload out = new ShowPingPayload(pos, payload.colorIndex(), player.getUUID());
for (ServerPlayer target : PlayerLookup.tracking(player.level(), pos)) {
ServerPlayNetworking.send(target, out);
}
}
}The handler already runs on the server thread (Fabric API documents this), so it may touch the world directly. Do not spawn your own threads to handle packets.
Server-side validation checklist:
- Never trust ids, positions, counts, slot indexes, entity ids, strings or NBT from a client.
- Check the entity still exists (
level().getEntity(id)), is the right type, and is within interaction range (closerThan,distanceToSqr). - Check permissions for anything admin-like (the vanilla permission check changed shape in
recent versions - look up how
ServerPlayerexposes permissions in your sources). - Check the chunk is loaded before reading blocks - loading chunks on request is a DoS vector.
- Clamp sizes of collections and strings; cap work per packet.
- Silently ignore (or log at debug) bad packets; do not crash or kick on the first bad value unless it is clearly malicious.
4. Receive on the client (client initializer, src/client)
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(ShowPingPayload.TYPE, (payload, context) -> {
if (context.client().level == null) return;
PingRenderer.add(payload.pos(), payload.colorIndex(), payload.from());
});
}Client handlers run on the client (render) thread. Keep them cheap: store the data, let rendering/tick code use it.
5. Send
// client -> server (client code only)
if (ClientPlayNetworking.canSend(PingBlockPayload.TYPE)) {
ClientPlayNetworking.send(new PingBlockPayload(pos, color));
}
// server -> one client
ServerPlayNetworking.send(serverPlayer, payload);
// server -> many: pick the narrowest PlayerLookup
PlayerLookup.tracking(entity); // players that see this entity
PlayerLookup.tracking(serverLevel, blockPos); // players with this chunk loaded
PlayerLookup.around(serverLevel, vec3, radius);
PlayerLookup.level(serverLevel);
PlayerLookup.all(server);canSendtells you whether the other side has your mod (i.e. registered a receiver for that type). Use it so a client-side feature degrades gracefully on servers without your mod, and so the server does not send packets to vanilla clients.- Prefer
tracking/aroundoverall: fewer packets, and players far away should not learn about things they cannot see.
Client/server separation
ClientPlayNetworking,Minecraft,LocalPlayer,ClientLevel->src/clientonly.ServerPlayNetworking,PayloadTypeRegistry, payload records ->src/main(common).- The logical server also runs inside the client (singleplayer). Never use
"am I on the client jar" checks to decide logic; use
level.isClientSide()for logical side. - A client-only mod (
"environment": "client") can still talk to a server that has a companion mod; it must checkcanSendand work fine when the server has nothing.
Configuration-phase payloads (advanced)
Data the client needs before joining the world (e.g. server-side config to sync) can use the
configuration phase: PayloadTypeRegistry.clientboundConfiguration() /
serverboundConfiguration() with ServerConfigurationNetworking and a configuration task.
Read the docs page for your version before using it - this area changed in several releases.
Common mistakes
- Using the pre-1.20.5
Identifier+PacketByteBufchannel API on modern versions, or the Yarn names (CustomPayload,PacketCodec) in a Mojang-named project. - Using
playS2C()/playC2S()on 26.1+ (renamed toclientboundPlay()/serverboundPlay()), or the new names on 1.21.x. - Registering the payload type only on one side, or only in the client initializer.
- Registering receivers in a static initializer that the dedicated server also loads, while referencing client classes from it.
- No server-side validation: trusting a client-sent position, entity id, amount, or "isCreative" flag. This is how dupes and grief exploits happen.
- Doing heavy work or blocking I/O inside a handler; or, conversely, wrapping a server
handler in
server.execute(...)out of habit (it already runs on the server thread). - Sending to
PlayerLookup.allevery tick instead of sending on change to tracking players. - Sending per-tick packets with full state instead of deltas or change events.
- Missing
canSendcheck, causing errors or kicks when joining vanilla/other servers. - Reusing one payload record for both directions with different meanings. Make one payload
per direction and purpose; name them clearly (
...ServerboundPayload/...ClientboundPayloadis the docs' convention).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

