MultiLoader 1.21+ · Part 4

Creating Blocks (MultiLoader 1.21+)

BEGINNER MULTILOADER 1.21-1.21.1 22 min read · Jun 1, 2026
MultiLoader 1.21+ · 28 parts
1 Getting Started with MultiLoader 1.21+ 2 Setting Up RegistrationUtils 3 Creating Items (MultiLoader 1.21+) 4 Creating Blocks (MultiLoader 1.21+) 5 Data Generation: Block & Item Models (MultiLoader 1.21+) 6 Data Generation: Block Loot Tables (MultiLoader 1.21+) 7 Data Generation: Crafting Recipes (MultiLoader 1.21+) 8 Data Generation: Block & Item Tags (MultiLoader 1.21+) 9 Custom Food Items (MultiLoader 1.21+) 10 Custom Tools (MultiLoader 1.21+) 11 Custom Armour (MultiLoader 1.21+) 12 Block Entities (MultiLoader 1.21+) 13 Config Files (MultiLoader 1.21+) 14 Custom Sounds (MultiLoader 1.21+) 15 Events and Listeners (MultiLoader 1.21+) 16 Networking and Custom Packets (MultiLoader 1.21+) 17 Data Generation: Advancements (MultiLoader 1.21+) 18 Data Generation: Language Files (MultiLoader 1.21+) 19 Custom Entities (MultiLoader 1.21+) 20 Ore Generation (MultiLoader 1.21+) 21 Introduction to Mixins (MultiLoader 1.21+) 22 Custom Particles (MultiLoader 1.21+) 23 Menus & Screens (MultiLoader 1.21+) 24 Key Bindings (MultiLoader 1.21+) 25 Custom Potion Effects (MultiLoader 1.21+) 26 Custom Enchantments (MultiLoader 1.21+) 27 Custom Commands (MultiLoader 1.21+) 28 Custom Biomes (MultiLoader 1.21+)

Now that we have items working, it's time to create a custom block. Blocks in Minecraft are closely tied to their items (the BlockItem is what you hold in your inventory), so our registry helper will handle registering both at once.

NOTE
Make sure you have completed the Creating Items tutorial first. We rely on the ItemRegistry here to register the BlockItem automatically.

Creating the Block Registry

Create a new BlockRegistry class in your registry package. We will add a private helper method called registerBlock that simultaneously registers the block and its corresponding BlockItem:

java
public class BlockRegistry {
public static final RegistrationProvider<Block> BLOCKS =
RegistrationProvider.get(Registries.BLOCK, Constants.MOD_ID);
private static <T extends Block> RegistryObject<Block, T> registerBlock(String name, Supplier<T> supplier) {
var block = BLOCKS.register(name, supplier);
ItemRegistry.ITEMS.register(name, () -> new BlockItem(block.get(), ItemRegistry.getItemProperties()));
return block;
}
public static void init() {}
}

The registerBlock helper registers the block under your mod's namespace, then immediately registers a matching BlockItem so the block is obtainable as an item. Both share the same registry name, which is the standard Minecraft convention.

Defining a Block

Add a public static final field for your first block. We will create a simple dirt variant called New Dirt by copying all properties from vanilla dirt:

java
public static final RegistryObject<Block, Block> NEW_DIRT = registerBlock("new_dirt",
() -> new Block(BlockBehaviour.Properties.ofFullCopy(Blocks.DIRT)));

ofFullCopy copies every property from the target block, including hardness, blast resistance, sound and tool requirement. You can also use the builder pattern (BlockBehaviour.Properties.of()...) to set properties manually.

Call BlockRegistry.init() from CommonClass.init():

java
public class CommonClass {
public static void init() {
ItemRegistry.init();
BlockRegistry.init();
CreativeTabRegistry.init();
}
}

Block Model and Texture

Create the block model JSON at src/main/resources/assets/examplemod/models/block/new_dirt.json:

json
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "examplemod:block/new_dirt"
}
}

The cube_all parent applies the same texture to every face. Create a 16×16 PNG texture at src/main/resources/assets/examplemod/textures/block/new_dirt.png.

Blocks also need an item model (for the inventory icon). Create src/main/resources/assets/examplemod/models/item/new_dirt.json that simply inherits the block model:

json
{
"parent": "examplemod:block/new_dirt"
}

Blockstate File

The blockstate file maps block property combinations to model variants. For a simple block with no properties, there is just one variant with an empty string key. Create src/main/resources/assets/examplemod/blockstates/new_dirt.json:

json
{
"variants": {
"": { "model": "examplemod:block/new_dirt" }
}
}
TIP
For more complex blocks (directional, powered, waterlogged, etc.) you add extra variants here. See the Minecraft Wiki on Block States for the full format.

Creative Tab for Blocks

Open CreativeTabRegistry and add a second tab for your blocks, or add the block to the existing items tab. Here we'll keep them separate:

java
public static final RegistryObject<CreativeModeTab> BLOCKS_TAB =
CREATIVE_MODE_TABS.register(Constants.MOD_ID + "_blocks_tab", () ->
CreativeModeTab.builder(CreativeModeTab.Row.TOP, 1)
.icon(() -> new ItemStack(BlockRegistry.NEW_DIRT.get()))
.displayItems((params, output) -> {
output.accept(BlockRegistry.NEW_DIRT.get());
})
.title(Component.translatable("itemGroup." + Constants.MOD_ID + ".blocks_tab"))
.build()
);

Language File

Add display names for the block and the new creative tab to your en_us.json language file:

json
{
"item.examplemod.iron_stick": "Iron Stick",
"block.examplemod.new_dirt": "New Dirt",
"itemGroup.examplemod.tab": "Example Mod Items",
"itemGroup.examplemod.blocks_tab": "Example Mod Blocks"
}

Testing In-Game

Run the Fabric Client or NeoForge Client. Open creative mode and you should see your new Blocks tab with New Dirt in it. Place the block and verify it renders correctly with your texture. You can also use:

shell
/give @s examplemod:new_dirt

Breaking the block will not drop anything yet, as that requires a loot table, which we cover in the Data Generation tutorials coming up next.

You can find the source for this tutorial here:

View Source on GitHub
NEXT IN SERIES

Data Generation: Block & Item Models (MultiLoader 1.21+)

Configure NeoForge datagen to write resources into the common project, create BlockStateProvider and ItemModelProvider classes, and hook them into GatherDataEvent to auto-generate model and blockstate files.

Continue →