Fabric blocks
Full reference for advanced block development in Fabric Minecraft 1.20.1 — block entities, server-side and client-side tickers, container blocks (inventories/slots), custom block states and properties, named screen handler factories, and block entity data persistence. Use this skill whenever the user asks about block entities, BlockEntityTicker, implementing Inventory on a block, custom BlockState properties, block entity NBT, container blocks, BlockEntityProvider, or anything beyond a simple static block. Triggers on: "block entity", "BlockEntity", "ticker", "BlockEntityTicker", "container block", "inventory block", "block state property", "custom block state", "block NBT", "ImplementedInventory", "chest-like block", or "block entity renderer". 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-blocksAssembled 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
10.9 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Fabric Blocks — 1.20.1 Reference
For simple blocks (no inventory, no custom state), see
fabric-mc-moddingfor registration. This skill covers block entities, tickers, containers, and custom block states.
Block Entity Basics
1. The Block class (must implement BlockEntityProvider)
public class MyBlock extends Block implements BlockEntityProvider {
public MyBlock(Settings settings) {
super(settings);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new MyBlockEntity(pos, state);
}
// Add ticker for server and/or client ticking
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(
World world, BlockState state, BlockEntityType<T> type) {
// Server-side ticker only:
if (!world.isClient) {
return checkType(type, ModBlockEntities.MY_BLOCK_ENTITY, MyBlockEntity::tick);
}
return null;
}
// Allow right-click to open screen
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos,
PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!world.isClient) {
BlockEntity be = world.getBlockEntity(pos);
if (be instanceof MyBlockEntity myBe) {
player.openHandledScreen(myBe);
}
}
return ActionResult.SUCCESS;
}
}
2. Register the block and block entity type
// Block registration
public class ModBlocks {
public static final Block MY_BLOCK = new MyBlock(
FabricBlockSettings.create().hardness(3.5f).requiresTool()
);
public static void register() {
Registry.register(Registries.BLOCK, new Identifier("mymod", "my_block"), MY_BLOCK);
Registry.register(Registries.ITEM, new Identifier("mymod", "my_block"),
new BlockItem(MY_BLOCK, new FabricItemSettings()));
}
}
// Block entity type registration
public class ModBlockEntities {
public static BlockEntityType<MyBlockEntity> MY_BLOCK_ENTITY;
public static void register() {
MY_BLOCK_ENTITY = FabricBlockEntityTypeBuilder
.create(MyBlockEntity::new, ModBlocks.MY_BLOCK)
.build();
Registry.register(Registries.BLOCK_ENTITY_TYPE,
new Identifier("mymod", "my_block_entity"), MY_BLOCK_ENTITY);
}
}
Block Entity with Ticker
public class MyBlockEntity extends BlockEntity {
private int tickCount = 0;
public MyBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.MY_BLOCK_ENTITY, pos, state);
}
// Static ticker method — called every server tick when block is loaded
public static void tick(World world, BlockPos pos, BlockState state, MyBlockEntity be) {
be.tickCount++;
if (be.tickCount % 20 == 0) { // every second
// do periodic work
}
}
// Persist data to NBT
@Override
protected void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
nbt.putInt("tick_count", tickCount);
}
@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
tickCount = nbt.getInt("tick_count");
}
}
Container Block Entity (Inventory)
Implements ImplementedInventory from Fabric API for easy Inventory support:
public class MyContainerBlockEntity extends BlockEntity
implements NamedScreenHandlerFactory, ImplementedInventory {
private final DefaultedList<ItemStack> inventory =
DefaultedList.ofSize(9, ItemStack.EMPTY);
public MyContainerBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.MY_CONTAINER, pos, state);
}
// ImplementedInventory — provide your backing list
@Override
public DefaultedList<ItemStack> getItems() {
return inventory;
}
// NamedScreenHandlerFactory — open GUI on right-click
@Override
public Text getDisplayName() {
return Text.translatable("container.mymod.my_container");
}
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) {
return new MyScreenHandler(syncId, inv, this);
}
// Sync to client on chunk load
@Override
public NbtCompound toInitialChunkDataNbt() {
return createNbt();
}
@Override
public Packet<ClientPlayPacketListener> toUpdatePacket() {
return BlockEntityUpdateS2CPacket.create(this);
}
@Override
protected void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
Inventories.writeNbt(nbt, inventory);
}
@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
Inventories.readNbt(nbt, inventory);
}
}
Dropping inventory on block break
In your Block class:
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos,
BlockState newState, boolean moved) {
if (!state.isOf(newState.getBlock())) {
BlockEntity be = world.getBlockEntity(pos);
if (be instanceof MyContainerBlockEntity container) {
ItemScatterer.scatter(world, pos, container);
world.updateComparators(pos, this);
}
super.onStateReplaced(state, world, pos, newState, moved);
}
}
Custom Block State Properties
Defining properties
public class MyDirectionalBlock extends Block implements BlockEntityProvider {
// Boolean property
public static final BooleanProperty POWERED =
BooleanProperty.of("powered");
// Integer property (0–3)
public static final IntProperty LEVEL =
IntProperty.of("level", 0, 3);
// Direction facing (horizontal)
public static final DirectionProperty FACING =
Properties.HORIZONTAL_FACING; // north/south/east/west
// Add properties to the state definition
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(POWERED, LEVEL, FACING);
}
// Set default state in constructor
public MyDirectionalBlock(Settings settings) {
super(settings);
setDefaultState(getStateManager().getDefaultState()
.with(POWERED, false)
.with(LEVEL, 0)
.with(FACING, Direction.NORTH));
}
// Set facing from player placement
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return getDefaultState()
.with(FACING, ctx.getHorizontalPlayerFacing().getOpposite());
}
}
Updating state
world.setBlockState(pos,
world.getBlockState(pos)
.with(MyDirectionalBlock.POWERED, true)
.with(MyDirectionalBlock.LEVEL, 2),
Block.NOTIFY_ALL
);
Blockstate JSON for Custom Properties
assets/mymod/blockstates/my_directional_block.json:
{
"variants": {
"facing=north,powered=false": { "model": "mymod:block/my_block" },
"facing=south,powered=false": { "model": "mymod:block/my_block", "y": 180 },
"facing=east,powered=false": { "model": "mymod:block/my_block", "y": 90 },
"facing=west,powered=false": { "model": "mymod:block/my_block", "y": 270 },
"facing=north,powered=true": { "model": "mymod:block/my_block_on" },
"facing=south,powered=true": { "model": "mymod:block/my_block_on", "y": 180 },
"facing=east,powered=true": { "model": "mymod:block/my_block_on", "y": 90 },
"facing=west,powered=true": { "model": "mymod:block/my_block_on", "y": 270 }
}
}
Comparator Output
// In your block class:
@Override
public boolean hasComparatorOutput(BlockState state) { return true; }
@Override
public int getComparatorOutput(BlockState state, World world, BlockPos pos) {
return ScreenHandler.calculateComparatorOutput(world.getBlockEntity(pos));
}
Block Entity Energy / Fluid (via Fabric API transfer)
For blocks that store energy or fluid, use the Fabric Transfer API:
// In block entity:
private final SingleVariantStorage<FluidVariant> fluidStorage =
new SingleVariantStorage<>() {
@Override protected FluidVariant getBlankVariant() { return FluidVariant.blank(); }
@Override protected long getCapacity(FluidVariant v) { return FluidConstants.BUCKET * 4; }
@Override protected void onFinalCommit() { markDirty(); }
};
// Expose via API (in block entity):
// Implement Storage<FluidVariant> and register with FluidStorage.SIDED
See the Fabric Transfer API docs for full patterns.
Common Mistakes
- Block entity type registered with wrong block —
FabricBlockEntityTypeBuilder.create(..., MY_BLOCK)must list every block that uses this entity type. - Ticker returns null on client — if you want client ticking (e.g. for animations), check
world.isClientand return a client ticker separately. - NBT not saving —
writeNbtandreadNbtmust callsuperand all data must be written/read symmetrically. - Screen not opening — block must return
ActionResult.SUCCESSfromonUse, and block entity must implementNamedScreenHandlerFactory. - Inventory not persisting — use
Inventories.writeNbt/readNbthelpers; don't write slots manually.
Scripts
| Script | What it does |
|---|---|
scripts/add_block_entity.py | Scaffold a BlockEntity + its host Block class |
# Basic block entity
python scripts/add_block_entity.py --mod-id mymod --group com.example --id my_machine
# With server tick and 9-slot inventory
python scripts/add_block_entity.py --mod-id mymod --group com.example --id my_furnace --ticker --inventory 9
Assets (Templates)
| File | Use for |
|---|---|
assets/blockstate_simple.json | Single-variant blockstate (cube block) |
assets/model_block_entity.json | Block model with particle texture (for BlockWithEntity) |
Related Skills
fabric-mc-modding— basic block/item registrationfabric-rendering— BlockEntityRenderer for visual effectsfabric-networking— syncing block entity data to clientsfabric-datagen— generating loot tables, models, tags for blocksminecraft-json-assets— writing blockstate/model JSON by hand
What ships with it: 3 files
6.1 KB alongside SKILL.md, 1 of them executable
assets/
scripts/
- add_block_entity.pyruns5.9 KB