Chapter 19 — Advancements: Detecting Player Events
What You’ll Build
So far everything your pack does, you set in motion: you /reload, or you run a function, or
a command in the tick tag fires twenty times a second whether anything interesting happened or
not. In this chapter you build something different: files that wait for the player to do
something and react the moment it happens.
By the end you’ll have two new files working in the pack you started in Chapter 9. The first is a normal-looking advancement: the first time a player steps into the Nether, a toast pops up in the corner and a function runs to welcome them. The second is sneakier: a hidden advancement with no popup at all. Its only job is to notice when a player eats a golden apple and quietly run a function. The player never sees the advancement screen change. To them it just feels like the world is paying attention.
That second pattern (an advancement whose only purpose is to detect an event and run a function) is one of the most useful tools in the whole book. You’ll lean on it when you build the bigger projects later on.
Figure (to be captured). the “Into the Nether” toast sliding in at the top-right as the player enters the Nether portal
Advancements are really event listeners
You already know advancements as a player: the screen you open with the L key, full of little framed icons across tabs like Minecraft, Nether, The End, Adventure, and Husbandry. When you complete one, a sliding toast notification appears in the top-right corner, often with a chat message and a little sound. The icons live in trees: each tab starts with a leftmost root advancement and branches outward, and you drag around to see the branches.
That’s the surface. Here’s the secret: an advancement is an event listener that happens to show a toast. An event listener is just a file that watches for one specific thing to happen in the game and does something when it does. Under the hood, every advancement says “watch for this event; when it fires, mark me complete (and maybe run a reward).” The popup, the icon, the tab are all optional display. Strip the display away and you’re left with a pure detector: a file that silently waits for an event and fires a reward. That is what makes advancements so useful for a data pack, and it’s the opposite of how most tutorials present them.
Modern Minecraft. Older tutorials treat advancements as “achievements you design for players to chase.” That’s one use. But the vanilla game itself uses display-less advancements as plumbing, for example the hidden advancements that quietly unlock recipes in your recipe book. The wiki notes that some advancements “lack a display so that they can utilize triggers and rewards instead of excessive commands,” and that leaving display off even loads a touch faster. Think of an advancement as a trigger plus a reward, with display bolted on only when you want the player to see it.
Where they live, and the smallest possible advancement
Advancements are JSON files. They go in your namespace under a folder named, like all your other data-pack folders, in the singular:
data/mypack/advancement/
Just like function/, recipe/, and predicate/, it’s advancement, not advancements —
singular, following the same folder convention you’ve used since Chapter 9 (if your version ever
disagrees, the game log from Chapter 10 will tell you the folder it expected). The filename (minus
.json) becomes the last part of the advancement’s id. A file at
data/mypack/advancement/into_the_nether.json has the id mypack:into_the_nether.
Every advancement needs exactly one required thing: a criteria block. Everything else
(display, requirements, rewards, parent) is optional. A criterion (the singular of criteria) is
one event to watch for. The smallest legal advancement is just one criterion:
mypack/data/mypack/advancement/tiny_example.json
{
"criteria": {
"got_dirt": {
"trigger": "minecraft:inventory_changed",
"conditions": {
"items": [
{
"items": ["minecraft:dirt"]
}
]
}
}
}
}
Let’s name every piece, because the shape repeats forever:
criteria— the object holding all the events to watch. Required.got_dirt— the criterion name. You invent this. It can be any text. You’ll refer back to it by this exact name later (inrequirements), so pick something readable.trigger— which event to listen for, written as an identifier likeminecraft:inventory_changed. This is the heart of the criterion. Required inside a criterion.conditions— optional extra tests that must also pass when the trigger fires. Here: “…and one of the items that just entered my inventory was dirt.” The exact fields you can put inconditionsdepend on which trigger you chose.
When the trigger fires and all its conditions pass, the criterion is marked complete. With only
one criterion and no requirements, completing it completes the whole advancement.
Under the Hood (skippable). The
conditionsblock always allows aplayerfield that tests the player who would receive the advancement, using the same kind of entity-condition object you met with predicates in Chapter 18. It can test an entity’stype,location,nbt,gamemode, and more. Some triggers also add their own fields on top:inventory_changedaddsitems,consume_itemaddsitem, and so on. So a criterion’sconditionsis “the standardplayertest, plus whatever extras this particular trigger offers.”
The triggers: the events you can listen for
There are several dozen triggers in vanilla Minecraft, far too many to memorize, and most you’ll never touch. The skill isn’t knowing them all; it’s knowing how to look one up and copy its shape. Here are the handful that cover most real detectors, each taken straight from the advancement reference:
| Trigger | Fires when… | Useful extra conditions |
|---|---|---|
minecraft:inventory_changed | the player’s inventory changes | items (what was added), slots |
minecraft:consume_item | the player finishes eating/drinking an item | item (which item) |
minecraft:player_killed_entity | the player kills a mob or player | entity (what died), killing_blow |
minecraft:changed_dimension | the player crosses between dimensions | from, to (dimension ids) |
minecraft:location | every 20 ticks (once a second), no matter what | player (test the player’s location) |
minecraft:tick | every single tick (20×/second) | player |
minecraft:placed_block | the player places a block | location (a predicate list) |
minecraft:bred_animals | the player breeds two animals | child, parent, partner |
Two of these deserve a flag right now, because beginners misuse them constantly:
minecraft:locationandminecraft:tickare polling triggers. They don’t wait for a player action. They just fire on a clock (locationonce a second,tickevery tick) and then check theirconditions. They’re how you answer “is the player currently somewhere / in some state?” rather than “did the player just do something?” Handy, but use them sparingly; atick-based advancement is checking 20 times a second.minecraft:changed_dimensionis the one that fires on crossing into a dimension. There’s also aminecraft:nether_traveltrigger, but read its description carefully: it “triggers when the player travels to the Nether and then returns to the Overworld.” That’s a round-trip, not an entry. For “first time entering the Nether,”changed_dimensionwithtoset to the Nether is the right tool, which is exactly what we’ll use.
Try It! Open the advancement reference and skim the trigger list. Pick one that sounds fun (
minecraft:slept_in_bed,minecraft:used_totem,minecraft:tame_animal) and read its “extra conditions.” Every one follows the same pattern: atriggerline plus an optionalconditionsobject. Once you’ve read three, you’ve read them all.
Reusing the Chapter 18 condition objects
You don’t have to learn the inside of conditions from scratch, because several triggers reuse the
exact condition objects you already met. When a trigger’s condition says it checks an entity
(like player_killed_entity’s entity field), that entity is described with the same kind of
entity check you saw in Chapter 18, one that can test type, location, distance, effects,
equipment, nbt, and more. When it checks a location (like changed_dimension’s cousin checks,
or a location test inside player), it uses location fields like biomes, block, dimension,
position, and structures. So a criterion like “killed a creeper” is just player_killed_entity with an
entity whose type is minecraft:creeper, copied from the reference:
mypack/data/mypack/advancement/killed_a_creeper.json
{
"criteria": {
"boom": {
"trigger": "minecraft:player_killed_entity",
"conditions": {
"entity": {
"type": "minecraft:creeper"
}
}
}
}
}
requirements: combining several criteria
By default, if an advancement has more than one criterion, the player must complete all of
them. Often that’s not what you want; sometimes “do any one of these” is the goal. The
requirements field gives you that control.
requirements is a list of lists (a JSON array of arrays). Each inner list is a group
that names some of your criteria. The rule, straight from the reference:
The advancement is granted when every group has at least one completed criterion in it.
That’s AND across the groups, OR inside each group. Two patterns cover almost everything:
“Complete all of them” (AND). Put each criterion in its own group:
"requirements": [
["criterion_a"],
["criterion_b"]
]
Two groups; each must have one completed criterion; so you need both. (This is also exactly what
you get for free if you omit requirements entirely.)
“Complete any one of them” (OR). Put all the criteria in a single group:
"requirements": [
["criterion_a", "criterion_b", "criterion_c"]
]
One group; it just needs one of its three criteria completed; so any of them grants the advancement.
What Can Go Wrong? Every name inside
requirementsmust be a criterion name you actually defined up incriteria. A typo there means that group can never be satisfied. And watch the brackets:requirementsis a list of lists.["a","b"](one group, OR) behaves very differently from[["a"],["b"]](two groups, AND). If your advancement “won’t fire even though the event happened,” this nesting is the first place to look.
rewards: what happens when it completes
When an advancement completes, it can hand out rewards. There are four, and the last one is the star of the show:
mypack/data/mypack/advancement/rewards_example.json
{
"criteria": {
"trigger_me": {
"trigger": "minecraft:tick"
}
},
"rewards": {
"experience": 10,
"recipes": ["mypack:chainmail_helmet"],
"loot": ["mypack:reward_chest"],
"function": "mypack:on_complete"
}
}
experience— an integer number of experience points to grant. Defaults to 0.recipes— a list of recipe ids to unlock in the player’s recipe book. This is the vanilla trick: hidden advancements with arecipesreward are how recipes get unlocked.loot— a list of loot-table ids; the player is given the items those tables roll. (Point it at one of your own tables from Chapters 16–17: here, an imaginedmypack:reward_chest.)function— a single function id to run. This is the one you’ll use most. It turns an advancement into a launcher for any function you can write, which means anything a data pack can do. Note one limit from the reference: it must be a function, not a function tag.
The function runs as the player who earned the advancement, at their position, so inside
it, @s is that player and ~ ~ ~ is where they are. That’s what makes the
advancement-fires-a-function pattern so natural: the function already knows who and where.
display: the part the player sees (or doesn’t)
The display block controls the toast, the icon, and where the advancement shows up on the
advancement screen. Leave display out entirely and the advancement still works; it just never
appears anywhere and never pops a toast. Here are its fields, from the reference:
icon— required if you include display. An object with an itemid(and optionalcount/components). This is the picture shown in the frame.title— required if you include display. A text component (Chapter 5): the name shown on the toast and in the screen.description— required if you include display. A text component: the hover text.frame— the frame style:task(the default),goal, orchallenge. They give the icon a different border and header;challengeis the fancy one that shows the pink “Challenge Complete!” header.background— only used by a root advancement: the texture behind that whole tab.show_toast—true/false; whether the corner toast appears. Defaults totrue.announce_to_chat—true/false; whether a chat message is posted. Defaults totrue.hidden—true/false; whether this advancement (and its children) stay invisible on the screen until completed. Defaults tofalse.
So you have two independent ways to keep things quiet, and they do different jobs:
"hidden": truehides the entry on the advancement screen until it’s earned, but the toast still pops when it completes.- Omitting
displayentirely is the real stealth move: no screen entry, no toast, no chat, nothing. The advancement exists purely to detect an event and fire its reward.
For a silent detector, leave display out. For a “secret achievement” the player can
discover, use display with "hidden": true.
parent advancements, roots, and tabs
Advancements form trees, and the parent field is what links them. Set parent to another
advancement’s id and yours becomes a child of it, drawn one column to the right with an arrow
pointing in. Leave parent out and your advancement is a root, and a root with valid
display data automatically creates a brand-new tab in the advancement menu. The background
field sets that tab’s backdrop. Children of a root appear inside its tab. For the detectors in this
chapter we won’t bother with tabs at all, but now you know how the vanilla trees are built.
Walkthrough A — “Into the Nether” (a visible advancement)
Let’s build the first real one: a normal advancement that pops a toast the first time a player enters the Nether, and runs a welcome function. Two files.
First the function it will run. It greets the player and gives a small gift. Remember, it runs
as and at the player, so @s is them:
mypack/data/mypack/function/nether_welcome.mcfunction
title @s actionbar {"text":"Welcome to the Nether — bring summer clothes!","color":"gold"}
effect give @s minecraft:fire_resistance 30 0
(If you want a sound too, add a playsound line using the Chapter 2 syntax; pick any sound id
you’ve confirmed exists in your version; we leave it off here to keep the listing to things we’ve
already grounded.)
Now the advancement. The event is “crossed into the Nether,” which is changed_dimension with to
set to the Nether dimension. We give it display so the player sees a toast, and a function
reward pointing at the file above:
mypack/data/mypack/advancement/into_the_nether.json
{
"display": {
"icon": {
"id": "minecraft:flint_and_steel"
},
"title": {
"text": "Into the Nether"
},
"description": {
"text": "Step through a Nether portal for the first time"
},
"frame": "task",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": {
"entered_nether": {
"trigger": "minecraft:changed_dimension",
"conditions": {
"to": "minecraft:the_nether"
}
}
},
"rewards": {
"function": "mypack:nether_welcome"
}
}
Save both, run /reload in your test world, and walk through a Nether portal. You should see the
“Into the Nether” toast slide in, the action-bar greeting appear, and gain Fire Resistance for 30
seconds. The advancement is now complete for that player, which means by itself it will only
ever fire once per player per world. For an “achievement,” that’s exactly right. For a detector
you want to fire over and over, we need one more trick, coming up next.
Figure (to be captured). advancement screen open (L key) showing the new “Into the Nether” task icon with its description tooltip
Under the Hood (skippable).
changed_dimensionalso accepts afromfield, so you could require a specific origin: say, only count entering the Nether from the Overworld with"from": "minecraft:overworld". We left it off so any route into the Nether counts.
Walkthrough B — the hidden golden-apple detector
Now the pattern this chapter is really about. We want: every time a player eats a golden apple, run a function, with no toast, no screen entry, nothing visible. And “every time,” not just once.
Two ideas combine here:
- No
displayblock → the advancement is a silent detector. The player never sees it. - The reward function revokes the advancement from the player at the end → this un-completes it, so it is armed again and will fire on the next golden apple too.
That second idea is the key to re-triggering. An advancement, once complete, won’t fire again for
that player, unless you take it back. The /advancement command does exactly that.
First the detector. The event is consume_item (it fires when the player finishes eating or
drinking something), and we narrow it with an item condition to golden apples. Notice: no
display field at all.
mypack/data/mypack/advancement/ate_golden_apple.json
{
"criteria": {
"ate_gapple": {
"trigger": "minecraft:consume_item",
"conditions": {
"item": {
"items": ["minecraft:golden_apple"]
}
}
}
},
"rewards": {
"function": "mypack:on_golden_apple"
}
}
Now the reward function. It does whatever you want and then revokes itself. The revoke line is
what re-arms the detector. The function runs as the eating player, so @s targets exactly
them:
mypack/data/mypack/function/on_golden_apple.mcfunction
title @s actionbar {"text":"The golden apple's magic flows through you...","color":"yellow"}
advancement revoke @s only mypack:ate_golden_apple
Read that last line carefully, because it’s the whole trick. From the /advancement command:
advancement (grant|revoke) <targets> only <advancement> [<criterion>]: adds or removes a single advancement.
So advancement revoke @s only mypack:ate_golden_apple takes the advancement back from this one
player, un-completing it. The next golden apple they eat re-fires consume_item, re-completes the
advancement, runs the function again, which revokes again… a perfect, reusable detector.
Save both files (ate_golden_apple.json and on_golden_apple.mcfunction), /reload, then eat a
golden apple (/give @s golden_apple 5 first if you need a few). You should get the action-bar
message every time, not just once.
Modern Minecraft. This “hidden advancement → function → revoke itself” loop is the standard way data packs react to player events that the game otherwise gives you no hook for: eating a specific food, killing a specific mob, picking up an item. It’s lighter and cleaner than scanning every player every tick with a
tick-tag function, because the game tells you the moment the event happens. Keep this pattern in your back pocket; you’ll reach for it constantly.
Practice
-
Pick a different food. Copy
ate_golden_apple.jsonto a new file (sayate_cake.json, idmypack:ate_cake) and change theitem’sitemsto a food you like, and point itsfunctionreward at a new function that does something fun and then revokesmypack:ate_cake. Confirm it re-fires every time. -
A combat detector. Build a hidden advancement (no display) using
minecraft:player_killed_entitywith anentitycondition whosetypeis a mob you choose, for exampleminecraft:zombie. Its reward function should reward the player (someexperienceor an item) and revoke the advancement so it fires on every kill of that mob. Tip: you can give experience two ways here, via the advancement’srewards.experienceor inside the function; pick one and notice the difference (the reward fires once per completion; the function fires every time the function runs). -
A visible “secret.” Take your “Into the Nether” advancement and make a second, harder one that you keep
"hidden": true(but still hasdisplay). Choose aframeof"challenge"so it shows the pink “Challenge Complete!” header, and watch how it stays invisible on the advancement screen until you earn it.
What Can Go Wrong
-
“My advancement only fired once.” That’s the default behavior: a completed advancement won’t re-fire for that player. If you want it to repeat, the reward function must
advancement revoke @s only <its own id>at the end, like the golden-apple detector. Forgetting that line is the single most common mistake with detectors. -
“Nothing happens at all.” Check three things in order: (1) the file is under
data/mypack/advancement/(singular folder); (2) thetriggerid is spelled exactly, with theminecraft:namespace; (3) yourconditionsaren’t too strict, so start with no conditions, get the trigger firing, then add conditions one at a time. A/reloadafter every edit, too. -
“The function reward errors or hits the wrong player.” The reward
functionruns as the player who earned it, so use@s, not@por@a. And it must be a plain function id, not a function tag; the reference explicitly disallows tags here.
What You Know Now
You can read an advancement as what it really is: an event listener, a trigger to watch for,
optional conditions to narrow it, optional requirements to combine several triggers (AND across
groups, OR within a group), rewards to fire when it completes (above all a function), and
display only when you actually want the player to see a toast. You learned the two stealth levels
("hidden": true, off the screen but still toasts, and no display at all, a truly silent
detector) and the revoke-to-re-arm loop that turns a one-shot advancement into a reusable event
hook. You can now build packs that respond to what players do: eating, killing, traveling,
entering a place. That reactive, hidden-detector pattern is the backbone of the bigger projects
ahead.