Developing Minecraft Fabric Mods
Standard Fabric mod structure (1.21.1, Java 21):
Java// src/main/java/com/example/examplemod/ExampleMod.java package com.example.examplemod; import net.fabricmc.api.ModInitializer; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.material.MapColor; import com.mojang.serialization.MapCodec; public class ExampleMod implements ModInitializer { public static final String MOD_ID = "examplemod"; public static final Block EXAMPLE_BLOCK = new Block( BlockBehaviour.Properties.of() .mapColor(MapColor.STONE) .strength(3.0f, 3.0f) .requiresCorrectToolForDrops() ); @Override public void onInitialize() { var blockId = ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_block"); var registeredBlock = net.minecraft.core.Registry.register( net.minecraft.core.registries.BuiltInRegistries.BLOCK, blockId, EXAMPLE_BLOCK ); net.minecraft.core.Registry.register( net.minecraft.core.registries.BuiltInRegistries.ITEM, blockId, new BlockItem(registeredBlock, new Item.Properties()) ); } }
fabric.mod.json entrypoint must reference com.example.examplemod.ExampleMod.
Progress checklist for any new mod feature:
- Confirm Minecraft version, mapping (Yarn/Mojmap), loom version, Java version (17 for ≤1.20.4, 21 for ≥1.20.5/1.21.x)
- Register content (block/item/entity) in
ModInitializer - Add lang file (
assets/<modid>/lang/en_us.json) - Add block/item model JSON (
assets/<modid>/models/block/andmodels/item/) - Add blockstate JSON if block has variants
- Add texture PNG (
assets/<modid>/textures/block/ortextures/item/), size must be power of 2 (16x16, 32x32...) - Add loot table if block should drop itself (
data/<modid>/loot_table/blocks/) - Build with
./gradlew buildand check for errors before delivering - Verify no missing texture (purple/black checker) by cross-checking every model references an existing PNG path
Version/Java mapping (critical, never mix these up):
| Minecraft version | Java | Loom (typical) |
|---|---|---|
| 1.8 – 1.16.5 | Java 8 | legacy loom |
| 1.17 – 1.18.2 | Java 17 (or 16) | loom 0.11+ |
| 1.19 – 1.20.4 | Java 17 | loom 1.x |
| 1.20.5 – 1.21.1 | Java 21 | loom 1.6+ |
| 1.21.11* (treat as latest 1.21.x) | Java 21 | loom latest |
*If a version number looks non-standard (e.g. "1.21.11"), assume the user means the latest stable 1.21.x and confirm which exact patch (1.21, 1.21.1, 1.21.4...) since mappings differ per patch — ask for clarification only if registry/API calls actually differ between patches, otherwise default to 1.21.1 conventions.
Example 1: Input: "Basit bir custom item ekle, sağ tıklayınca can versin." Output:
Javapublic static final Item HEALING_APPLE = new Item(new Item.Properties().food( new FoodProperties.Builder().nutrition(4).saturationModifier(0.3f).build() ));
Registered via Registry.register(BuiltInRegistries.ITEM, ResourceLocation.fromNamespaceAndPath(MOD_ID, "healing_apple"), HEALING_APPLE);
Model: assets/modid/models/item/healing_apple.json → {"parent": "item/generated", "textures": {"layer0": "modid:item/healing_apple"}}
Texture: 16x16 PNG at assets/modid/textures/item/healing_apple.png.
Example 2: Input: "Blockstate ekle, block'un 4 yöne bakması lazım (facing)." Output:
JSON{ "variants": { "facing=north": { "model": "modid:block/example_block" }, "facing=south": { "model": "modid:block/example_block", "y": 180 }, "facing=east": { "model": "modid:block/example_block", "y": 90 }, "facing=west": { "model": "modid:block/example_block", "y": 270 } } }
Plus Java: implement HorizontalDirectionalBlock, override getStateForPlacement and createBlockStateDefinition.
- Always use
ResourceLocation.fromNamespaceAndPath(modid, path)(1.21+ API) — the oldnew ResourceLocation(...)constructor is removed/deprecated. - Keep texture resolution consistent per mod (all 16x16, or all 32x32) — never mix.
- Model file
parentmust match block shape:block/cube_all,block/cross(plants),item/generated(flat items),item/handheld(tools). - Register content in
onInitialize, never in static initializers that run before registries exist. - Use
DeferredRegister-style helper classes for large mods to avoid registration order bugs (even in Fabric, a simple static holder class per content type keeps things clean). - Always add lang entries — missing translation shows
block.modid.nameas literal text, which looks like a bug. - Test in dev environment (
./gradlew runClient) before declaring done. - When unsure of exact MC version API (fields/methods renamed almost every version), state the assumption explicitly rather than guessing silently.
- Mixing Java 17 code/build config with a 1.21.x project expecting Java 21 → runtime
UnsupportedClassVersionError. - Forgetting
requiresCorrectToolForDrops()causes block to drop even with wrong tool (fine for some blocks, wrong for ores). - Texture path typo (
textures/block/vstextures/blocks/) → invisible/purple-black checkerboard texture. - Forgetting item model file entirely → item shows as missing-texture cube in inventory even if block model is fine.
- Using outdated
Registry.registeroverloads or removedResourceLocationconstructors from pre-1.21 tutorials. - Not adding a loot table → breaking the block drops nothing.
- Copy-pasting mixin code across versions without checking method signatures — Minecraft internals change often; verify against the actual mapped source (via
./gradlew genSourcesor Linkie/Yarn dashboard) instead of assuming.