MultiLoader 26.1+ · Part 5

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

INTERMEDIATE MULTILOADER 26.1-26.1.2 25 min read · Jun 12, 2026
MultiLoader 26.1+ · 17 parts
1 Getting Started with MultiLoader 26.1+ 2 Setting Up RegistrationUtils (MultiLoader 26.1+) 3 Creating Items (MultiLoader 26.1+) 4 Creating Blocks (MultiLoader 26.1+) 5 Data Generation: Block & Item Models (MultiLoader 26.1+) 6 Data Generation: Block Loot Tables (MultiLoader 26.1+) 7 Data Generation: Crafting Recipes (MultiLoader 26.1+) 8 Data Generation: Block & Item Tags (MultiLoader 26.1+) 9 Data Generation: Language Files (MultiLoader 26.1+) 10 Data Generation: Advancements (MultiLoader 26.1+) 11 Data Generation: Sound Definitions (MultiLoader 26.1+) 12 Data Generation: Particle Descriptions (MultiLoader 26.1+) 13 Data Generation: Enchantments (MultiLoader 26.1+) 14 Custom Food Items (MultiLoader 26.1+) 15 Custom Tools (MultiLoader 26.1+) 16 Custom Armour (MultiLoader 26.1+) 17 Block Entities (MultiLoader 26.1+)

Manually writing JSON model, blockstate, and item definition 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, then replace our hand-authored files with a single generated provider.

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.

Model Provider

In 26.1, the model system was reworked. Block models, blockstate files, and item definition files are all generated through a single ModelProvider class rather than separate BlockStateProvider and ItemModelProvider classes from older NeoForge versions.

In your NeoForge subproject, create a data package and add a class called ExampleModelProvider extending ModelProvider. Override registerModels, which receives a BlockModelGenerators for block resources and an ItemModelGenerators for standalone item models:

java
public class ExampleModelProvider extends ModelProvider {
public ExampleModelProvider(PackOutput output) {
super(output, Constants.MOD_ID);
}
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// Generates blockstates/new_dirt.json and models/block/new_dirt.json
blockModels.createTrivialCube(BlockRegistry.NEW_DIRT.get());
// Generates items/new_dirt.json pointing at the block model
blockModels.registerSimpleItemModel(
BlockRegistry.NEW_DIRT.get(),
ModelLocationUtils.getModelLocation(BlockRegistry.NEW_DIRT.get()));
// Generates models/item/iron_stick.json and items/iron_stick.json
blockModels.registerSimpleFlatItemModel(ItemRegistry.IRON_STICK.get());
}
}

createTrivialCube generates a cube_all block model using the block's registry name as the texture path, plus the matching blockstate file. It does not automatically produce an item definition, so you follow it immediately with registerSimpleItemModel to write the items/ definition that points the inventory slot at the block model.

registerSimpleFlatItemModel handles standalone items in one call: it creates a minecraft:item/generated model JSON and then writes the matching items/ definition that references it.

TIP
For blocks that need a shape other than a plain cube, BlockModelGenerators has helpers such as createAxisAlignedPillarBlock for logs and pillars, createDoor, createTrapdoor, and createSlab. Use them the same way in place of createTrivialCube.

Registering the Provider

Model providers are client-side resources, so they are registered through GatherDataEvent.Client rather than the general GatherDataEvent. In your NeoForge mod class constructor, add a listener:

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

Then add the static handler method:

java
public static void gatherClientData(GatherDataEvent.Client event) {
event.createProvider(output -> new ExampleModelProvider(output));
}

event.createProvider wires the provider into the generator automatically, handling pack output routing and file writing. Server-side providers (loot tables, recipes, tags) will be registered through a separate GatherDataEvent listener in later tutorials.

Running Datagen

Before running the generator, delete the existing hand-authored files from your common project's resources. Remove the blockstates/, models/block/, models/item/, and items/ directories, 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 folder appear in your common project containing five files:

  • assets/examplemod/blockstates/new_dirt.json
  • assets/examplemod/models/block/new_dirt.json
  • assets/examplemod/items/new_dirt.json
  • assets/examplemod/models/item/iron_stick.json
  • assets/examplemod/items/iron_stick.json
TIP
Every time you add a new block or item, add a corresponding call in registerModels and re-run NeoForge Data. The generator overwrites only the files it manages, leaving your textures and other hand-authored resources untouched.

In the next tutorial we use datagen 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 26.1+)

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 →