MultiLoader 1.21+ · Part 6

Data Generation: Block Loot Tables (MultiLoader 1.21+)

INTERMEDIATE MULTILOADER 1.21-1.21.1 15 min read · Jun 15, 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+)

After the previous tutorial your blocks render correctly in-game, but breaking them drops nothing. That is because Minecraft requires a loot table for each block to know what items to drop. In this tutorial we generate those loot tables automatically using the same datagen setup we already have.

NOTE
Complete Data Generation: Block & Item Models before continuing. We add a new provider to the existing gatherData method.

Block Loot Provider

In your NeoForge data package, create a class called ExampleBlockLootTableProvider extending BlockLootSubProvider:

java
public class ExampleBlockLootTableProvider extends BlockLootSubProvider {
protected ExampleBlockLootTableProvider(HolderLookup.Provider registries) {
super(Set.of(), FeatureFlags.REGISTRY.allFlags(), registries);
}
@Override
protected void generate() {
dropSelf(BlockRegistry.NEW_DIRT.get());
}
@Override
protected Iterable<Block> getKnownBlocks() {
return BlockRegistry.BLOCKS.getEntries().stream()
.map(RegistryObject::get)
.collect(Collectors.toList());
}
}

The generate() method is where you define what each block drops. Right now we just call dropSelf so the block drops itself when broken with the correct tool.

getKnownBlocks() tells the generator which blocks this provider is responsible for. Using BlockRegistry.BLOCKS.getEntries() means every block you register will automatically be validated. If you forget to add a loot table for a block, datagen will throw an error rather than silently producing an incomplete jar.

Registering the Provider

Open your NeoForge mod class and update the gatherData method to also accept the loot registries parameter and register the new provider. The updated method looks like this:

java
public static void gatherData(GatherDataEvent event) {
try {
DataGenerator generator = event.getGenerator();
PackOutput output = generator.getPackOutput();
ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
CompletableFuture<HolderLookup.Provider> registries = event.getLookupProvider();
generator.addProvider(true,
new ExampleItemModelProvider(output, existingFileHelper));
generator.addProvider(true,
new ExampleBlockStateProvider(output, Constants.MOD_ID, existingFileHelper));
generator.addProvider(true,
new LootTableProvider(output, Set.of(),
List.of(new LootTableProvider.SubProviderEntry(
ExampleBlockLootTableProvider::new,
LootContextParamSets.BLOCK
)), registries));
} catch (RuntimeException e) {
Constants.LOG.error("Failed to generate data", e);
}
}

The key difference from before is the additional HolderLookup.Provider registries argument obtained from the event, which the loot provider needs to resolve data pack objects.

TIP
To add more block loot rules later, add extra dropSelf (or other) calls inside generate(). Common helpers include:
  • dropSelf(block): drops the block itself
  • createOreDrop(block, item): drops an ore's yield, affected by Fortune
  • createSlabItemTable(block): correctly doubles the drop count for slabs

Running Datagen

Run the NeoForge Data configuration. After it finishes, check the common/src/generated/resources/data/examplemod/loot_table/blocks/ folder. You should see a new_dirt.json file that looks like this:

json
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"name": "examplemod:new_dirt"
}
],
"rolls": 1.0
}
]
}

Testing In-Game

Launch the client and place a New Dirt block in survival mode. Break it; it should now drop itself. Try breaking it with a Silk Touch pickaxe to confirm the survives_explosion condition does not affect normal drops.

WARNING
If the block still does not drop anything, double-check that the generated loot table file is inside data/examplemod/loot_table/blocks/ (note: loot_table, not loot_tables). The path changed in 1.21.

You can find the source for this tutorial here:

View Source on GitHub
NEXT IN SERIES

Data Generation: Crafting Recipes (MultiLoader 1.21+)

Create a RecipeProvider to generate shaped crafting recipes, shapeless recipes, and smelting and blasting recipes via datagen, hooking everything into GatherDataEvent alongside your existing providers.

Continue →