Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 (in requirements), so pick something readable.
  • triggerwhich event to listen for, written as an identifier like minecraft: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 in conditions depend 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 conditions block always allows a player field 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’s type, location, nbt, gamemode, and more. Some triggers also add their own fields on top: inventory_changed adds items, consume_item adds item, and so on. So a criterion’s conditions is “the standard player test, 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:

TriggerFires when…Useful extra conditions
minecraft:inventory_changedthe player’s inventory changesitems (what was added), slots
minecraft:consume_itemthe player finishes eating/drinking an itemitem (which item)
minecraft:player_killed_entitythe player kills a mob or playerentity (what died), killing_blow
minecraft:changed_dimensionthe player crosses between dimensionsfrom, to (dimension ids)
minecraft:locationevery 20 ticks (once a second), no matter whatplayer (test the player’s location)
minecraft:tickevery single tick (20×/second)player
minecraft:placed_blockthe player places a blocklocation (a predicate list)
minecraft:bred_animalsthe player breeds two animalschild, parent, partner

Two of these deserve a flag right now, because beginners misuse them constantly:

  • minecraft:location and minecraft:tick are polling triggers. They don’t wait for a player action. They just fire on a clock (location once a second, tick every tick) and then check their conditions. 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; a tick-based advancement is checking 20 times a second.
  • minecraft:changed_dimension is the one that fires on crossing into a dimension. There’s also a minecraft:nether_travel trigger, 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_dimension with to set 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: a trigger line plus an optional conditions object. 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 requirements must be a criterion name you actually defined up in criteria. A typo there means that group can never be satisfied. And watch the brackets: requirements is 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 a recipes reward 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 imagined mypack: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:

  • iconrequired if you include display. An object with an item id (and optional count / components). This is the picture shown in the frame.
  • titlerequired if you include display. A text component (Chapter 5): the name shown on the toast and in the screen.
  • descriptionrequired if you include display. A text component: the hover text.
  • frame — the frame style: task (the default), goal, or challenge. They give the icon a different border and header; challenge is the fancy one that shows the pink “Challenge Complete!” header.
  • background — only used by a root advancement: the texture behind that whole tab.
  • show_toasttrue/false; whether the corner toast appears. Defaults to true.
  • announce_to_chattrue/false; whether a chat message is posted. Defaults to true.
  • hiddentrue/false; whether this advancement (and its children) stay invisible on the screen until completed. Defaults to false.

So you have two independent ways to keep things quiet, and they do different jobs:

  • "hidden": true hides the entry on the advancement screen until it’s earned, but the toast still pops when it completes.
  • Omitting display entirely 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_dimension also accepts a from field, 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:

  1. No display block → the advancement is a silent detector. The player never sees it.
  2. 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

  1. Pick a different food. Copy ate_golden_apple.json to a new file (say ate_cake.json, id mypack:ate_cake) and change the item’s items to a food you like, and point its function reward at a new function that does something fun and then revokes mypack:ate_cake. Confirm it re-fires every time.

  2. A combat detector. Build a hidden advancement (no display) using minecraft:player_killed_entity with an entity condition whose type is a mob you choose, for example minecraft:zombie. Its reward function should reward the player (some experience or 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’s rewards.experience or inside the function; pick one and notice the difference (the reward fires once per completion; the function fires every time the function runs).

  3. A visible “secret.” Take your “Into the Nether” advancement and make a second, harder one that you keep "hidden": true (but still has display). Choose a frame of "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) the trigger id is spelled exactly, with the minecraft: namespace; (3) your conditions aren’t too strict, so start with no conditions, get the trigger firing, then add conditions one at a time. A /reload after every edit, too.

  • “The function reward errors or hits the wrong player.” The reward function runs as the player who earned it, so use @s, not @p or @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.