Fabric events
Deep reference for the Fabric API event system for Minecraft 1.20.1 modding. Use this skill whenever a Fabric mod task involves listening to game events, registering callbacks, creating custom events, choosing between Fabric events vs Mixins, or troubleshooting event-related code. Triggers on: "how do I listen for...", "what event fires when...", "register a callback", "ServerTickEvents", "PlayerBlockBreakEvents", "AttackEntityCallback", "UseItemCallback", "ServerLifecycleEvents", "custom event", "Event.create", or any question about hooking into game logic without a Mixin. Part of the fabric-mc-modding skill family.From its SKILL.md
npx -y skills add PSB1234/Fabric-Minecraft-Skill-1.20.1 --skill fabric-eventsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.2 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
Fabric Events — 1.20.1 Reference
Rule of thumb: If Fabric API has an event for what you need, use it instead of a Mixin. Events are safer, more mod-compatible, and easier to read. Use Mixins only when no suitable event exists.
How Events Work
Event<CallbackInterface> — stores all registered listeners
└─ .register(callback) — add your listener lambda
└─ .invoker() — call all listeners (used internally by the game)
All event registrations go inside onInitialize() (or onInitializeClient() for
client-only events). Never register events lazily or from static initializers.
// Pattern A — single static EVENT field on the callback interface
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
return ActionResult.PASS;
});
// Pattern B — grouped events class (multiple related events)
ServerTickEvents.END_SERVER_TICK.register(server -> { /* ... */ });
ActionResult / InteractionResult Return Values
Many interaction callbacks return ActionResult to control whether the game continues:
| Value | Effect |
|---|---|
ActionResult.PASS | Continue normal game logic |
ActionResult.SUCCESS | Stop processing, play swing animation |
ActionResult.CONSUME | Stop processing, no swing animation |
ActionResult.FAIL | Prevent default action, no swing |
For item-use callbacks that return TypedActionResult<ItemStack>:
TypedActionResult.pass(stack) // continue
TypedActionResult.success(stack) // stop, swing
TypedActionResult.fail(stack) // cancel
Interaction Events (Player Actions)
These live in net.fabricmc.fabric.api.event.player.
// Left-click a block (mining start)
AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
// world is World on client, ServerWorld on server — check side if needed
return ActionResult.PASS;
});
// Left-click an entity
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
return ActionResult.PASS;
});
// Right-click a block
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
return ActionResult.PASS;
});
// Right-click an entity
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
return ActionResult.PASS;
});
// Right-click with an item (no block/entity target)
UseItemCallback.EVENT.register((player, world, hand) -> {
ItemStack stack = player.getStackInHand(hand);
return TypedActionResult.pass(stack);
});
⚠️ These fire before the spectator check. Always guard:
if (player.isSpectator()) return ActionResult.PASS;
Block Events
// net.fabricmc.fabric.api.event.player
// Before a player breaks a block — return false to cancel
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, entity) -> {
return true; // false = cancel break
});
// After a player breaks a block (block already removed)
PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> { });
// When break is cancelled (by BEFORE returning false, or by another mod)
PlayerBlockBreakEvents.CANCELED.register((world, player, pos, state, entity) -> { });
Server Lifecycle Events
net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
// Server is starting — player manager and worlds not yet loaded
ServerLifecycleEvents.SERVER_STARTING.register(server -> { });
// Server is fully started — all worlds live, safe to query data
ServerLifecycleEvents.SERVER_STARTED.register(server -> { });
// Server beginning shutdown — worlds still accessible
ServerLifecycleEvents.SERVER_STOPPING.register(server -> { });
// Server fully stopped — worlds closed, entities unloaded
ServerLifecycleEvents.SERVER_STOPPED.register(server -> { });
// Before /reload — good place to save cached data
ServerLifecycleEvents.START_DATA_PACK_RELOAD.register((server, resourceManager) -> { });
// After /reload — success boolean indicates if reload succeeded
ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> { });
// Server is about to send recipes/tags to a player (e.g. on login or reload)
ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.register((player, joined) -> { });
Server Tick Events
net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
// Start/end of the entire server tick
ServerTickEvents.START_SERVER_TICK.register(server -> { });
ServerTickEvents.END_SERVER_TICK.register(server -> { });
// Start/end of a specific world's tick (fires once per world per tick)
ServerTickEvents.START_WORLD_TICK.register(world -> { });
ServerTickEvents.END_WORLD_TICK.register(world -> { });
💡 Use
END_WORLD_TICKfor starting async computations for the next tick. Dedicated servers may "pause" when empty — none of these fire while paused.
Server World Events
net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents
// A dimension/world was loaded
ServerWorldEvents.LOAD.register((server, world) -> { });
// A dimension/world is about to unload
ServerWorldEvents.UNLOAD.register((server, world) -> { });
Server Entity & Block Entity Events
net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents /
net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents
// Entity loaded into a server world
ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> { });
// Entity about to be unloaded
ServerEntityEvents.ENTITY_UNLOAD.register((entity, world) -> { });
// Block entity loaded
ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> { });
// Block entity about to be unloaded
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> { });
Player Connection Events
net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents
// Player finished joining (world loaded, safe to interact with player)
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = handler.player;
});
// Player disconnected
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { });
Loot Table Events
net.fabricmc.fabric.api.loot.v2.LootTableEvents
// Add items to an existing loot table without replacing it
LootTableEvents.MODIFY.register((key, tableBuilder, source) -> {
if (source.isBuiltin() && LootTables.COAL_ORE_GAMEPLAY.equals(key)) {
LootPool.Builder pool = LootPool.builder()
.rolls(ConstantLootNumberProvider.create(1))
.with(ItemEntry.builder(Items.EGG));
tableBuilder.pool(pool);
}
});
Client Lifecycle Events
Register these inside onInitializeClient() only.
net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
// Minecraft client started (splash screen still showing)
ClientLifecycleEvents.CLIENT_STARTED.register(client -> { });
// Minecraft client is stopping
ClientLifecycleEvents.CLIENT_STOPPING.register(client -> { });
Client Tick Events
net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
ClientTickEvents.START_CLIENT_TICK.register(client -> { });
ClientTickEvents.END_CLIENT_TICK.register(client -> { });
ClientTickEvents.START_WORLD_TICK.register(world -> { });
ClientTickEvents.END_WORLD_TICK.register(world -> { });
HUD Render Event
net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback
HudRenderCallback.EVENT.register((drawContext, tickDelta) -> {
drawContext.drawText(
MinecraftClient.getInstance().textRenderer,
Text.literal("Hello HUD"),
5, 5, 0xFFFFFF, true
);
});
Screen Events
net.fabricmc.fabric.api.client.screen.v1.ScreenEvents
// After a screen is initialized (safe to add widgets)
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
if (screen instanceof TitleScreen) {
// add custom widgets to the screen here
}
});
// Before a screen is initialized
ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> { });
Command Registration
net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(CommandManager.literal("mycommand")
.executes(ctx -> {
ctx.getSource().sendFeedback(
() -> Text.literal("Hello!"), false);
return 1;
})
);
});
Item Group Events (Creative Tab)
net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents
// Add items to an existing creative tab
ItemGroupEvents.modifyEntriesEvent(ItemGroups.COMBAT).register(content -> {
content.add(ModItems.MY_SWORD);
});
Creating a Custom Event
Use this when no existing Fabric event covers your use case.
Step 1 — Define the callback interface
@FunctionalInterface
public interface MyCustomCallback {
// Static EVENT field is the convention
Event<MyCustomCallback> EVENT = EventFactory.createArrayBacked(
MyCustomCallback.class,
(listeners) -> (player, world) -> {
for (MyCustomCallback listener : listeners) {
listener.onMyThing(player, world);
}
}
);
void onMyThing(PlayerEntity player, World world);
}
Step 2 — Fire it from a Mixin
@Mixin(SheepEntity.class)
public class SheepEntityMixin {
@Inject(
at = @At(value = "INVOKE",
target = "Lnet/minecraft/entity/passive/SheepEntity;sheared(Lnet/minecraft/sound/SoundCategory;)V"),
method = "interactMob",
cancellable = true
)
private void onShear(PlayerEntity player, Hand hand, CallbackInfoReturnable<ActionResult> cir) {
ActionResult result = MyCustomCallback.EVENT.invoker().onMyThing(player, this.getWorld());
if (result == ActionResult.FAIL) {
cir.setReturnValue(ActionResult.FAIL);
}
}
}
Step 3 — Listen to it
MyCustomCallback.EVENT.register((player, world) -> {
// your logic here
return ActionResult.PASS;
});
Event Quick-Picker: "What event do I need?"
| Goal | Event |
|---|---|
| Run code every tick | ServerTickEvents.END_SERVER_TICK |
| Detect player joining | ServerPlayConnectionEvents.JOIN |
| Detect player leaving | ServerPlayConnectionEvents.DISCONNECT |
| Cancel a block break | PlayerBlockBreakEvents.BEFORE (return false) |
| React after block break | PlayerBlockBreakEvents.AFTER |
| Intercept item right-click | UseItemCallback.EVENT |
| Intercept block right-click | UseBlockCallback.EVENT |
| Intercept entity right-click | UseEntityCallback.EVENT |
| Intercept left-click entity | AttackEntityCallback.EVENT |
| Add drops to loot table | LootTableEvents.MODIFY |
| Run on server start | ServerLifecycleEvents.SERVER_STARTED |
| Add a command | CommandRegistrationCallback.EVENT |
| Add to creative tab | ItemGroupEvents.modifyEntriesEvent(...) |
| Draw on player HUD | HudRenderCallback.EVENT |
| Hook into screen | ScreenEvents.AFTER_INIT |
| Entity spawn/despawn | ServerEntityEvents.ENTITY_LOAD/UNLOAD |
| No event exists for this | → Write a Mixin; consider creating a custom event |
Common Mistakes
- Registering events in a static initializer — must be in
onInitialize()/onInitializeClient(). - Using a server event on the client —
ServerTickEventswon't fire in single-player unless you're on the logical server side. Checkworld instanceof ServerWorldwhen needed. - Forgetting the spectator guard on interaction callbacks — they fire before Minecraft's own spectator check.
- Mutating state inside
LootTableEvents.MODIFYoutside the condition — always checksource.isBuiltin()and the specific loot table key before modifying. - Client events in the
mainentrypoint —ClientTickEvents,HudRenderCallback,ScreenEvents, etc. must be registered inClientModInitializer, notModInitializer.
Further Reading
- Full event index: https://wiki.fabricmc.net/tutorial:event_index
- Fabric API Javadoc (1.20.1): https://maven.fabricmc.net/docs/fabric-api-0.92.2+1.20.1/
- Custom events guide: https://docs.fabricmc.net/1.20.4/develop/events
- EventFactory source: https://github.com/FabricMC/fabric/tree/1.20.1/fabric-api-base
References
| File | When to load |
|---|---|
references/event-index.md | Complete event listing — every event class, package, callback signature, and notes. Load when looking up a specific event or exploring what's available. |
What ships with it: 1 file
10.4 KB alongside SKILL.md
references/
- event-index.md10.4 KB