Chapter 32 — Project: A Custom Achievement Tree
What You’ll Build
Back in Chapter 19 you learned the secret of advancements: each one is really an event listener that happens to show a toast. You built two of them on their own. In this chapter you put a whole group of them together into something the player actually sees as a feature: a custom advancement tab, your own page in the advancement screen, with its own background, its own root, and a tree of linked achievements branching across it.
By the end you’ll have a themed tab called Explorer’s Path living in the mypack pack you’ve
been growing since Chapter 9. It has a root advancement that creates the tab, three visible
achievements that get progressively harder (easy → medium → hard), each linked to the one before it,
and one hidden advancement tucked inside that works as a silent re-armable detector (the exact
pattern from Chapter 19, now used in a real project). Several of the advancements hand out custom
items you build from data components (Chapters 21–24) through small reward functions. At the end
you’ll learn to use /advancement grant and /advancement revoke to cheat-test the tree
and, beyond that, as a way to read and set a player’s progress like a switch.
Nothing in this chapter is a brand-new Minecraft idea. Every file uses pieces you already know. The skill this chapter teaches is assembly: taking systems you’ve learned one at a time and wiring them into one coherent thing. That is what real data packs are.
Figure (to be captured). the advancement screen with a new “Explorer’s Path” tab selected, showing the root icon on the left and arrows branching to three child advancements
First, design the tree — on paper
Before writing a single file, decide what the tab is. A good advancement tree has three things:
- A theme. Ours is exploration — venturing out, going underground, facing the dragon. The theme decides the icons, titles, and the background art.
- A root. Every tab starts with one root advancement: an advancement with no
parent. From Chapter 19 you know a root with valid display data automatically creates a new tab in the advancement menu. The root is the leftmost icon; everything else hangs off it. - A progression. Children branch rightward from the root, each one linked to a parent, getting harder as you go. We’ll do three tiers:
| Advancement | Difficulty | How you earn it | Frame |
|---|---|---|---|
| Explorer’s Path (root) | — | given at the start (a tick trigger) | task |
| First Steps | easy | pick up a map | task |
| Cave Delver | medium | be deep underground | goal |
| Dragon Slayer | hard | kill the Ender Dragon | challenge |
| (secret snack) | hidden | eat a golden carrot (re-arms) | (no display) |
That last row is the Chapter 19 trick: a hidden, display-less detector that lives inside the same tab’s files but never shows on the screen. Designing it in now, on paper, is how professionals work: the structure exists before the JSON does.
A note on folders. All of this still lives in the singular advancement/ folder from Chapter 19.
To keep a project tidy, we’ll put every file for this tree in a subfolder named explorer/. A
file at data/mypack/advancement/explorer/root.json has the id mypack:explorer/root: the subfolder
becomes part of the id, exactly like it did for functions. Grouping a project’s files in a subfolder
named for the project is a convention worth keeping for every project from here on.
The root: creating the tab
The root is the most important file because it builds the tab. It has no parent (that’s what
makes it a root), full display data so the tab appears, and the one field we’ve mentioned but
never used: background.
From the advancement reference, background is “the directory for the background to use in this
advancement tab (used only for the root advancement)”, and the reference adds that “each tab has a
different background with a repeating texture.” So background points at a texture path that tiles
behind your tab. Any valid texture path works; the listing below uses
minecraft:textures/block/stone.png only as an example so we don’t have to make art yet. Treat
the exact path as a placeholder and confirm a real texture path when you reach resource packs and
textures in Chapter 29. (If the path is wrong, the game just shows the missing-texture pattern behind
the tab; the tab still works.)
We also need the root to actually grant itself, or the tab will sit there grayed-out forever. The
simplest way is a tick trigger with no conditions. It completes the instant the player exists,
which makes the root a “you’ve started” marker. From Chapter 19, minecraft:tick fires every tick;
with nothing to test, it completes immediately.
mypack/data/mypack/advancement/explorer/root.json
{
"display": {
"icon": {
"id": "minecraft:compass"
},
"title": {
"text": "Explorer's Path"
},
"description": {
"text": "Your journey begins"
},
"frame": "task",
"background": "minecraft:textures/block/stone.png",
"show_toast": false,
"announce_to_chat": false,
"hidden": false
},
"criteria": {
"started": {
"trigger": "minecraft:tick"
}
}
}
Every field here is one you met in Chapter 19; the only new one is background, and it does exactly
what the reference says: sets the art behind this tab. We turned show_toast and announce_to_chat
off so the player isn’t spammed with a “you exist!” popup at world join; the tab still appears once
they open the advancement screen.
Save it, run /reload in your test world, and press L. A new Explorer’s Path tab should be
there, with a lonely compass icon. Now let’s give it some branches.
What Went Wrong? No new tab appears. A root only makes a tab if it has valid display data, and
displayis only valid if it includesicon,title, anddescription(the reference marks all three required oncedisplayis present). Leave one out and the wholedisplayis ignored, so the advancement becomes a silent one and no tab is drawn. If your tab is missing, check those three fields first, then check the file is underadvancement/(singular).
The branches: progressive children linked by parent
Now the tiers. Each child sets parent to the id of the advancement before it, which draws it one
column to the right with an arrow pointing in. The frames climb with the difficulty: task for easy,
goal for medium, challenge for the showstopper.
Tier 1 — easy. “First Steps”: earned by picking up a map. We use inventory_changed (fires when
the inventory changes) narrowed with an items condition, the same item-condition object from
Chapter 19, whose items field “tests if the type of item in the item stack matches any of the
listed values.” Its parent is the root.
mypack/data/mypack/advancement/explorer/first_steps.json
{
"parent": "mypack:explorer/root",
"display": {
"icon": {
"id": "minecraft:map"
},
"title": {
"text": "First Steps"
},
"description": {
"text": "Hold a map and head out"
},
"frame": "task"
},
"criteria": {
"got_map": {
"trigger": "minecraft:inventory_changed",
"conditions": {
"items": [
{
"items": ["minecraft:map"]
}
]
}
}
},
"rewards": {
"function": "mypack:explorer/give_compass"
}
}
Notice the rewards.function: when the player earns “First Steps”, it runs a function that hands
them a custom item. We’ll write that function in the next section. Notice too that this child has no
background: the reference says background is root-only, so children just inherit the tab’s.
Tier 2 — medium. “Cave Delver”: earned by being deep underground. This one uses the polling
location trigger from Chapter 19 (it fires once a second and checks the player’s situation), with a
location condition testing the player’s Y position. From the location-condition object, position
takes x/y/z ranges; we test that y is low. Its parent is “First Steps”, so it draws to the
right of it. Frame is goal.
mypack/data/mypack/advancement/explorer/cave_delver.json
{
"parent": "mypack:explorer/first_steps",
"display": {
"icon": {
"id": "minecraft:torch"
},
"title": {
"text": "Cave Delver"
},
"description": {
"text": "Descend deep below the surface"
},
"frame": "goal"
},
"criteria": {
"went_deep": {
"trigger": "minecraft:location",
"conditions": {
"player": [
{
"condition": "minecraft:location_check",
"predicate": {
"position": {
"y": {
"max": 0
}
}
}
}
]
}
}
},
"rewards": {
"function": "mypack:explorer/give_lantern"
}
}
Here the criterion’s player is written in its list form. The reference notes that player can
be “a list of predicates that must pass.” Each entry is a predicate object exactly like the
Chapter 18 ones: a condition of minecraft:location_check whose predicate carries the location
fields. So “is the player at Y 0 or below?” reuses the predicate vocabulary you already have.
Tier 3 — hard. “Dragon Slayer”: kill the Ender Dragon. This is player_killed_entity (fires when
the player kills something) with an entity condition whose type is the dragon (the entity-
condition object from Chapter 19). Parent is “Cave Delver”; frame is challenge, the one that shows
the pink “Challenge Complete!” header and plays the big sound.
mypack/data/mypack/advancement/explorer/dragon_slayer.json
{
"parent": "mypack:explorer/cave_delver",
"display": {
"icon": {
"id": "minecraft:dragon_head"
},
"title": {
"text": "Dragon Slayer"
},
"description": {
"text": "Defeat the Ender Dragon"
},
"frame": "challenge",
"show_toast": true,
"announce_to_chat": true
},
"criteria": {
"slew_dragon": {
"trigger": "minecraft:player_killed_entity",
"conditions": {
"entity": {
"type": "minecraft:ender_dragon"
}
}
}
},
"rewards": {
"experience": 500,
"function": "mypack:explorer/dragon_reward"
}
}
Two rewards stack here: a flat experience of 500 (an integer reward straight from the reference) and
a function that grants the trophy item. You can combine reward types freely; they all fire when the
advancement completes.
/reload, open the L screen, and you’ll see the tab now has the compass root with First
Steps → Cave Delver → Dragon Slayer marching to the right, each arrow pointing at the next. The tree
exists. Now let’s make its rewards real.
Figure (to be captured). the Explorer’s Path tab fully populated — compass root, then map / torch / dragon-head icons connected by arrows, the dragon-head one wearing the fancy challenge frame
Under the Hood (skippable). The game arranges the rows for you. Each advancement draws an arrow from its closest visible ancestor, so if you ever insert a display-less advancement in the middle of a chain, the arrow simply skips it and links to its grandparent. That’s why a hidden detector (next section) can sit inside the tree’s files without disturbing the picture the player sees.
Reward functions that grant custom items
The whole reason rewards.function matters (Chapter 19) is that a function can do anything, and
the most satisfying thing a project can do is hand the player a custom item they can’t get any
other way. We build those items out of data components (Chapters 21–24) and give them with /give.
Remember the rule from Chapter 19: a reward function runs as and at the player who earned the
advancement, so @s is them. Each function below gives one themed item, named and described with the
custom_name and lore components from Chapter 21, using the [component=value] bracket form you
learned there.
The “First Steps” reward, an explorer’s compass with a name and a lore line:
mypack/data/mypack/function/explorer/give_compass.mcfunction
give @s minecraft:compass[custom_name={text:"Pathfinder's Compass",color:"aqua",italic:false},lore=[{text:"Points the way onward.",color:"gray"}]]
title @s actionbar {"text":"Reward: Pathfinder's Compass","color":"aqua"}
Both components trace to the item-component reference: custom_name is “the player-assigned name of this item… See
Text component format,” so its value is a text component (Chapter 5); lore is a “list of
additional lines… Text component representing a line of text,” so its value is a list of text
components. One renamed, lore-bearing compass, given the moment the player earns the achievement.
The “Cave Delver” reward is a lantern that doubles as a snack, using the food component from
Chapter 23 so a deep-cave explorer never starves:
mypack/data/mypack/function/explorer/give_lantern.mcfunction
give @s minecraft:lantern[custom_name={text:"Everlight Lantern",color:"gold",italic:false},food={nutrition:4,saturation:2,can_always_eat:true}]
title @s actionbar {"text":"Reward: Everlight Lantern","color":"gold"}
From the food reference, nutrition is the food points restored, saturation the saturation, and
can_always_eat:true means “this item can be eaten even if the player is not hungry.” (In a finished
pack you’d also add the consumable component from Chapter 23 to control the eating animation; we keep
this listing to the one component the reward needs.)
The “Dragon Slayer” trophy, a named, lore-stamped dragon head:
mypack/data/mypack/function/explorer/dragon_reward.mcfunction
give @s minecraft:dragon_head[custom_name={text:"Dragonslayer's Trophy",color:"light_purple",italic:false},lore=[{text:"Slayer of the Ender Dragon.",color:"dark_purple"},{text:"Explorer's Path complete.",color:"gray"}]]
title @s actionbar {"text":"TROPHY EARNED","color":"light_purple","bold":true}
That lore value is a list with two entries, drawing two tooltip lines, exactly what the
reference allows. /reload and test by granting yourself an advancement (the next section shows how),
and you’ll get the item in hand, fully named and described, with no model or texture work at all. The
components do everything.
Modern Minecraft. Older tutorials built “custom” reward items with long
/give ... {NBT}blobs or by reading raw NBT. The modern way is exactly what you see here: pick a base item, override a few components in brackets, done. The reward item is just an ordinary/givewith components: the same skill from Chapter 21, now paying off inside a real project.
A hidden detector living inside the tree
A project tab can hold more than the achievements the player sees. We’ll add the Chapter 19
hidden-detector pattern inside the explorer/ folder: an advancement with no display at
all, whose only job is to notice an event and run a function, every time, by revoking itself to
re-arm. Here it watches for the player eating a golden carrot, a little secret snack that heals.
mypack/data/mypack/advancement/explorer/secret_snack.json
{
"criteria": {
"ate_carrot": {
"trigger": "minecraft:consume_item",
"conditions": {
"item": {
"items": ["minecraft:golden_carrot"]
}
}
}
},
"rewards": {
"function": "mypack:explorer/secret_snack"
}
}
No parent, no display, so it is not a second tab and not a visible node. The reference is
explicit that advancements which “lack a display… should not have the display field defined in
order to hide from users.” It simply sits in the files as plumbing. And the reward function re-arms
it, the way Chapter 19 taught: do something, then revoke the advancement from @s so it can fire
again.
mypack/data/mypack/function/explorer/secret_snack.mcfunction
effect give @s minecraft:regeneration 5 0
title @s actionbar {"text":"A warm glow spreads through you...","color":"green"}
advancement revoke @s only mypack:explorer/secret_snack
That last line is the whole trick, copied from the /advancement command:
advancement (grant|revoke) <targets> only <advancement>, which “adds or removes a single
advancement.” Revoking it un-completes it, so the next golden carrot fires consume_item again. A
perfect reusable hook, hidden inside the same project as the showy achievements. /reload, eat a
golden carrot (/give @s golden_carrot 5 first), and you should get Regeneration every time.
Using grant and revoke as advancement-based state
The /advancement command isn’t only for re-arming detectors. Because an advancement is either
completed or not for each player, it doubles as a simple on/off state you can set and check:
“has this player finished the Explorer’s Path?” is just “do they have mypack:explorer/dragon_slayer?”
Granting, to test. While building, you don’t want to actually kill the dragon every time. Grant yourself an advancement straight from chat to fire its rewards and check the whole chain:
/advancement grant @s only mypack:explorer/first_steps
From the command reference, grant ... only <advancement> adds that single advancement, which fires
its reward function, so this is how you test give_compass without finding a map. There’s also a
sweeping form: advancement grant <targets> from <advancement> “adds… an advancement and all its
child advancements.” So to unlock the whole tree for a test run:
/advancement grant @s from mypack:explorer/root
That grants the root and everything branching off it in order: instant full tree.
Revoking, to reset. To wipe your progress and start the tab fresh (handy when testing the visible-vs-hidden behavior), revoke from the root:
/advancement revoke @s from mypack:explorer/root
Now the tab is back to its starting state for you.
Reading state in a function. Because completion is per-player, you can gate later content on an advancement the same way you gate on a scoreboard or a tag. You already have the tools: a reward function on the final advancement can set a marker the rest of your pack reads. For example, the dragon reward could tag the player as a finisher so other systems can react:
mypack/data/mypack/function/explorer/dragon_reward.mcfunction (extended)
give @s minecraft:dragon_head[custom_name={text:"Dragonslayer's Trophy",color:"light_purple",italic:false},lore=[{text:"Slayer of the Ender Dragon.",color:"dark_purple"},{text:"Explorer's Path complete.",color:"gray"}]]
tag @s add explorer_complete
title @s actionbar {"text":"TROPHY EARNED","color":"light_purple","bold":true}
Now any function in your pack can check @s[tag=explorer_complete] (Chapter 13) to know whether a
player has finished the tree. The advancement drove a piece of game state. That is the real power
of grant/revoke: the achievement tree is a working progress system the rest of
your pack can build on.
Practice
-
Add a fourth tier. Branch a new advancement off “Cave Delver” (its
parentismypack:explorer/cave_delver) for a different exploration goal (saybred_animalsfor “Trail Companion”, tame the wild) with its owngoalframe and a reward function granting a named lead or saddle. Confirm the new arrow appears in the tab. -
Two paths from one parent. Give the root two easy children instead of one: “First Steps” (the map) and a sibling earned a different way (e.g.
placed_blockfor setting a campfire). Both setparenttomypack:explorer/root. Watch the tab fork into two branches. -
A second hidden detector. Copy
secret_snack.jsonto a new display-less file insideexplorer/that watches a different event (for instanceplayer_killed_entityon aminecraft: bat) and whose reward function does something fun and thenadvancement revoke @s only <its own id>. Confirm it re-fires every time and never shows on the tab. -
Gate a reward on completion. Write a function that uses
execute if entity @s[tag= explorer_complete] run ...(from your extended dragon reward) to give a bonus only to players who finished the tree, proving an advancement can act as a gate for later content.
What Can Go Wrong
-
“My tab is grayed-out / empty.” A tab only shows advancements whose
displayis valid, and the root must grant itself or nothing lights up. Make sure the root hasicon+title+descriptionand a criterion that actually completes (theticktrigger completes immediately). If the whole tab is missing, the root’sdisplayis probably invalid (a missing required field). -
“A child floats off on its own / isn’t linked.” Its
parentmust be the exact id of another advancement in your tree, including theexplorer/subfolder, e.g.mypack:explorer/first_steps, notmypack:first_steps. A wrong or misspelledparenteither errors on load or makes the child a stray root. Check the id matches the file path. -
“The reward item gives but looks plain.” The components are the item; if the name or lore didn’t apply, the bracket syntax is the suspect:
=(not:) between a component and its value, commas between components, andloremust be a list[ ... ]of text components even for one line. Re-read Chapter 21’s bracket rules if/givecomplained. -
“The hidden detector only worked once.” Same as Chapter 19: a completed advancement won’t re-fire until you take it back. The reward function must end with
advancement revoke @s only <its own id>. Forgetting that line is the classic detector bug.
What You Know Now
You can build a complete, themed advancement tab from parts you already had: a root that
creates the tab and sets its background, a chain of parent-linked children climbing from
task to goal to challenge frames, reward functions that grant component-built custom items
the moment each is earned, and a hidden, re-armable detector living quietly inside the same
project folder. You learned to drive the whole thing with /advancement grant and /advancement revoke: granting from the root to unlock the tree for testing, revoking to reset it, and using a
completed advancement as a piece of game state the rest of your pack can read. That is a real,
shippable feature, several systems assembled into something a player experiences as
a single progression. The next project chapter builds a minigame the same way: many small
systems, one coherent whole.