MultiLoader 1.21+ · Part 5

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

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

Manually writing JSON model and blockstate files is tedious and error-prone. NeoForge's data generation system can produce these files automatically. In this tutorial we configure it to output directly into the common project so Fabric picks them up too.

NOTE
Complete the Creating Blocks tutorial first. We generate resources for the NEW_DIRT block and IRON_STICK item from those tutorials.

Configuring Common Output

By default NeoForge datagen writes into a generated folder inside the NeoForge subproject. We want the output in common instead, so both loaders can reference the same files.

Open the common subproject's build.gradle and make two changes. First, add the generated resources directory as a source set before the dependencies block:

groovy
sourceSets.main.resources {
srcDir file('../common/src/generated/resources').getAbsolutePath()
}

Second, update the artifacts block so the common jar includes all resource directories (not just the first one):

groovy
artifacts {
commonJava sourceSets.main.java.sourceDirectories.singleFile
for (def dir : sourceSets.main.resources.sourceDirectories.files) {
commonResources dir
}
}

Updating NeoForge Data Config

Open the neoforge subproject's build.gradle and update the data run configuration block so it outputs to common and uses your existing resources to resolve conflicts:

groovy
data {
data()
programArguments.addAll '--mod', project.mod_id,
'--all',
'--output', file('../common/src/generated/resources').getAbsolutePath(),
'--existing', file('../common/src/main/resources/').getAbsolutePath()
}

The --output flag routes generated files into common, and --existing tells the generator to resolve any file conflicts against your manually authored resources.

Hit the Gradle refresh button after saving both files.

BlockState Provider

In your NeoForge subproject, create a data package and add a class called ExampleBlockStateProvider extending BlockStateProvider:

java
public class ExampleBlockStateProvider extends BlockStateProvider {
public ExampleBlockStateProvider(PackOutput output, String modid,
ExistingFileHelper existingFileHelper) {
super(output, modid, existingFileHelper);
}
@Override
protected void registerStatesAndModels() {
simpleBlockWithItem(BlockRegistry.NEW_DIRT.get(),
cubeAll(BlockRegistry.NEW_DIRT.get()));
}
private String name(Block block) {
return this.key(block).getPath();
}
private ResourceLocation key(Block block) {
return BuiltInRegistries.BLOCK.getKey(block);
}
}

simpleBlockWithItem generates three things at once: the blockstate JSON, the block model JSON, and the item model JSON that inherits the block model. The cubeAll call creates a cube_all model using the block's registry name as the texture path.

TIP
Keep the name() and key() helpers, as they come in handy when you need to build custom model paths for more complex blocks later.

Item Model Provider

In the same data package, create ExampleItemModelProvider extending ItemModelProvider:

java
public class ExampleItemModelProvider extends ItemModelProvider {
public ExampleItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
super(output, Constants.MOD_ID, existingFileHelper);
}
@Override
protected void registerModels() {
basicItem(ItemRegistry.IRON_STICK.get());
}
}

basicItem generates a minecraft:item/generated model JSON for flat 2D items. Add one basicItem call per item that is not a BlockItem (block items are already handled by the BlockState provider above).

Registering the Providers

In your NeoForge mod main class constructor, register a listener for GatherDataEvent:

java
// Inside your NeoForge mod class constructor:
eventBus.addListener(ExampleMod::gatherData);

Then add the static handler method:

java
public static void gatherData(GatherDataEvent event) {
try {
DataGenerator generator = event.getGenerator();
PackOutput output = generator.getPackOutput();
ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
generator.addProvider(true,
new ExampleItemModelProvider(output, existingFileHelper));
generator.addProvider(true,
new ExampleBlockStateProvider(output, Constants.MOD_ID, existingFileHelper));
} catch (RuntimeException e) {
Constants.LOG.error("Failed to generate data", e);
}
}

Running Datagen

Before running the generator, delete the existing hand-authored blockstates/ and models/ directories from your common project's resources (but keep your textures, as these are not generated).

Open the Run Configurations dropdown and select NeoForge Data. After it completes successfully you will see a generated (or generated (main)) folder appear in your common project containing the blockstate, block model and item model files:

TIP
Every time you add a new block or item, simply add a corresponding call in your providers and re-run NeoForge Data. The generator will overwrite only the files it manages, leaving your textures and other hand-authored resources untouched.

In the next tutorial we will use the same datagen setup to generate block loot tables so your blocks actually drop items when broken.

You can find the source for this tutorial here:

View Source on GitHub
NEXT IN SERIES

Data Generation: Block Loot Tables (MultiLoader 1.21+)

Write a BlockLootSubProvider that makes your custom blocks drop themselves, add it to a LootTableProvider, register everything through GatherDataEvent, and verify drops in-game.

Continue →