MultiLoader 26.1+ · Part 10

Data Generation: Advancements (MultiLoader 26.1+)

INTERMEDIATE MULTILOADER 26.1-26.1.2 15 min read · Jun 14, 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+)

Advancements are the in-game achievement system that also drives recipe unlocks. Rather than hand-writing JSON advancement files, you can generate them through datagen using AdvancementProvider. This tutorial creates a small advancement tree rooted on picking up the Iron Stick, with a child for obtaining New Dirt.

NOTE
Complete Data Generation: Crafting Recipes before continuing. We add a new provider to the existing gatherData listener.

Advancement Sub-Provider

AdvancementProvider does not generate advancements itself — it delegates to a list of AdvancementSubProvider instances, each of which generates a logical group of advancements. Create a class implementing the interface in your NeoForge data package:

java
public class ExampleAdvancementProvider implements AdvancementSubProvider {
@Override
public void generate(HolderLookup.Provider registries,
Consumer<AdvancementHolder> saver) {
// advancement definitions go here
}
}

The saver consumer writes each finished advancement to disk. You call save(saver, identifier) at the end of each builder chain, passing it the consumer and the advancement's resource location.

Root Advancement

Every advancement tree needs a root node. Root advancements have no parent, display a background texture, and typically do not announce in chat or show a toast so they do not spam the player. Use Advancement.Builder.advancement() to start a builder with telemetry enabled:

java
AdvancementHolder root = Advancement.Builder.advancement()
.display(
new ItemStack(ItemRegistry.IRON_STICK.get()),
Component.translatable("advancements.examplemod.root.title"),
Component.translatable("advancements.examplemod.root.description"),
Identifier.fromNamespaceAndPath("minecraft",
"textures/gui/advancements/backgrounds/stone.png"),
AdvancementType.TASK,
false, // showToast
false, // announceToChat
false) // hidden
.addCriterion("has_iron_stick",
InventoryChangeTrigger.TriggerInstance.hasItems(ItemRegistry.IRON_STICK.get()))
.save(saver, Identifier.fromNamespaceAndPath(Constants.MOD_ID, "root"));

The seven arguments after the background path are: the AdvancementType (TASK, GOAL, or CHALLENGE), whether to show a toast notification, whether to announce in chat, and whether to hide the advancement from the tab until it is earned. Add this call inside generate().

TIP
You can reuse any of Minecraft's built-in background textures by pointing at a path under minecraft:textures/gui/advancements/backgrounds/. Common choices are stone.png, dirt.png, and netherrack.png.

Child Advancement

Child advancements call parent(AdvancementHolder) with the holder returned by the parent's save call. Pass null as the background since only root advancements display one. Here we reward the player for picking up a New Dirt block and show a toast:

java
Advancement.Builder.advancement()
.parent(root)
.display(
new ItemStack(BlockRegistry.NEW_DIRT.get()),
Component.translatable("advancements.examplemod.new_dirt.title"),
Component.translatable("advancements.examplemod.new_dirt.description"),
null,
AdvancementType.TASK,
true, // showToast
true, // announceToChat
false)
.addCriterion("has_new_dirt",
InventoryChangeTrigger.TriggerInstance.hasItems(BlockRegistry.NEW_DIRT.get()))
.save(saver, Identifier.fromNamespaceAndPath(Constants.MOD_ID, "new_dirt"));

The advancement translation keys (advancements.examplemod.root.title, etc.) are plain language file entries. Add them to your ExampleLanguageProvider using the raw add(String, String) overload:

java
add("advancements.examplemod.root.title", "Example Mod");
add("advancements.examplemod.root.description", "Begin your journey with the Example Mod.");
add("advancements.examplemod.new_dirt.title", "Dirty Hands");
add("advancements.examplemod.new_dirt.description", "Obtain a New Dirt block.");

Registering the Provider

Advancements are server-side data, so register the provider inside gatherData. Pass a list containing your sub-provider to AdvancementProvider:

java
public static void gatherData(GatherDataEvent event) {
DataGenerator generator = event.getGenerator();
PackOutput output = generator.getPackOutput();
CompletableFuture<HolderLookup.Provider> registries = event.getLookupProvider();
generator.addProvider(true,
new LootTableProvider(output, Set.of(),
List.of(new LootTableProvider.SubProviderEntry(
ExampleBlockLootTableProvider::new,
LootContextParamSets.BLOCK
)), registries));
generator.addProvider(true,
new ExampleRecipeProvider.Runner(output, registries));
generator.addProvider(true,
new ExampleBlockTagsProvider(output, registries));
generator.addProvider(true,
new ExampleItemTagsProvider(output, registries));
generator.addProvider(true,
new AdvancementProvider(output, registries,
List.of(new ExampleAdvancementProvider())));
}

Running Datagen

Run the NeoForge Data configuration. Check common/src/generated/resources/data/examplemod/advancement/ for two new files:

json
// data/examplemod/advancement/root.json (excerpt)
{
"criteria": {
"has_iron_stick": {
"trigger": "minecraft:inventory_changed",
"conditions": { "items": [{ "items": "examplemod:iron_stick" }] }
}
},
"display": {
"icon": { "id": "examplemod:iron_stick" },
"title": { "translate": "advancements.examplemod.root.title" },
"description": { "translate": "advancements.examplemod.root.description" },
"background": "minecraft:textures/gui/advancements/backgrounds/stone.png",
"frame": "task",
"show_toast": false,
"announce_to_chat": false,
"hidden": false
}
}
TIP
To add multi-step criteria that all need to be met, call addCriterion multiple times and then call requirements(AdvancementRequirements.allOf(...)) or requirements(AdvancementRequirements.anyOf(...)) to control whether the player needs all of them or just one.

You can find the source for this tutorial here:

View Source on GitHub
NEXT IN SERIES

Data Generation: Sound Definitions (MultiLoader 26.1+)

Register a custom SoundEvent with RegistrationProvider, generate the sounds.json entry using SoundDefinitionsProvider, and add a subtitle translation key.

Continue →